home *** CD-ROM | disk | FTP | other *** search
/ EnigmA Amiga Run 1998 July / EnigmA AMIGA RUN 29 (1998)(G.R. Edizioni)(IT)[!][issue 1998-07 & 08].iso / earkit / news / thor / rexx / cfgsortmail.thor < prev    next >
Text File  |  1998-05-24  |  92KB  |  1,937 lines

  1. /*
  2. ** $VER: CfgSortMail.thor 3.43 (22.9.97)
  3. ** by Eirik Nicolai Synnes
  4. **
  5. ** See SortMail.guide for documentation
  6. **
  7. */
  8.  
  9. options results
  10. options failat 31
  11.  
  12. /*signal on error*/
  13. signal on syntax
  14. signal on break_c
  15. signal on halt
  16.  
  17. parse arg arguments
  18.  
  19. /*
  20. ** Initialize some variables
  21. */
  22.  
  23. globals = 'version mvhelp thorport thorpath cfgfile data. head. text. textread globalcfg. trigger. temp. atemp. stemp. menulist. bbslist. conflist. bbsdata. cursys. progwin currentmsg. addrs. BBSREAD.LASTERROR THOR.LASTERROR globals'
  24.  
  25. version = subword(sourceline(2), 4, 1)
  26.  
  27. cfgfile = 'SortMail.cfg'
  28.  
  29.  
  30. /*
  31. ** Check for Thor's ARexx port
  32. */
  33.  
  34. thorport = address()
  35. if left(thorport, 5) ~= 'THOR.' then do
  36.     say 'CfgSortMail.thor can only be run from within Thor.'
  37.     signal cleanup
  38. end
  39.  
  40.  
  41. /*
  42. ** Find/open BBSREAD ARexx port
  43. */
  44.  
  45. if ~show('P', 'BBSREAD') then do
  46.     address(command)
  47.     'Run >NIL: `GetEnv THOR/THORPath`bin/LoadBBSRead'
  48.     'WaitForPort BBSREAD'
  49.     if rc ~= 0 then call displayerror(30, 'Couldn''t open BBSREAD''s ARexx port.')
  50. end
  51.  
  52.  
  53. /*
  54. ** See if another copy of CfgSortMail is already running
  55. */
  56.  
  57. address(thorport)
  58. if getclip('CSM_Active') ~= '' then if ~reqnotify('Another copy of CfgSortMail is probably running.\nDo you want to continue?') then signal cleanup
  59. call setclip('CSM_Active', 'True')
  60.  
  61.  
  62. /*
  63. ** Get Thor's path
  64. */
  65.  
  66. call open(pn, 'ENV:Thor/THORPATH', 'R')
  67. thorpath = readln(pn)
  68. call close(pn)
  69.  
  70.  
  71. /*
  72. ** Find the configuration file
  73. */
  74.  
  75. address(thorport)
  76. 'CURRENTSYSTEM STEM 'cursys
  77. if (rc > 1) then call displayerror(30, 'CURRENTSYSTEM: 'THOR.LASTERROR)
  78.  
  79. address(bbsread)
  80. 'GETBBSDATA "'cursys.BBSNAME'" 'bbsdata
  81. if (rc ~= 0) then call displayerror(rc, 'GETBBSDATA: 'BBSREAD.LASTERROR)
  82. if (right(bbsdata.BBSPATH, 1) ~= ':') & (right(bbsdata.BBSPATH, 1) ~= '/') then bbsdata.BBSPATH = bbsdata.BBSPATH || '/'
  83.  
  84.  
  85. /*
  86. ** Get list of all systems
  87. */
  88.  
  89. address(bbsread)
  90. 'GETBBSLIST 'bbslist
  91. if rc > 0 then call displayerror(rc, 'GETBBSLIST:\n'BBSREAD.LASTERROR)
  92.  
  93.  
  94. /*
  95. ** Check Workbench version
  96. */
  97.  
  98. address command 'Version >NIL: workbench.library 40'
  99. if (rc = 0) then mvhelp = 1; else mvhelp = 0
  100.  
  101. /*
  102. ** Read configuration
  103. */
  104.  
  105. call readcfg()
  106.  
  107. /*
  108. ** Display main menu
  109. */
  110.  
  111. mainmenu:
  112. drop movetrig
  113.  
  114. menulist.1  = 'Email conference:           'globalcfg.CONFERENCE
  115. menulist.2  = 'Sort outgoing messages:     '; if (symbol('globalcfg.LOCALTO') = 'VAR') then menulist.2 = menulist.2 || 'YES, to "'globalcfg.LOCALTO'"'; else menulist.2 = menulist.2 || 'NO'
  116. menulist.3  = ''
  117. menulist.4  = 'Add new trigger'
  118. menulist.5  = 'Edit a trigger'
  119. menulist.6  = 'Rearrange triggers'
  120. menulist.7  = 'Delete a trigger'
  121. menulist.8  = ''
  122. menulist.9  = 'Auto configuration'
  123. menulist.10 = ''
  124. menulist.11 = 'Save and exit'
  125. menulist.count = 11
  126.  
  127. choice = showmenu('CfgSortMail 'version, 7)
  128.  
  129. select
  130.     when choice = 0 then if reqnotify('Are you sure you want\nto exit without saving?') then signal cleanup
  131.  
  132.     when choice = 1 then do
  133.         address(bbsread)
  134.         'GETCONFLIST BBSNAME "'globalcfg.SYSTEM'" STEM 'conflist
  135.         if (rc > 0) then call displayerror(rc, 'GETCONFLIST:\n'BBSREAD.LASTERROR)
  136.         if (conflist.COUNT = 0) then do
  137.             reqnotify('No conferences created on this\nsystem. Using EMail as default.', 'Ok')
  138.             globalcfg.CONFERENCE = 'EMail'
  139.         end
  140.         else do
  141.             address(thorport)
  142.             'REQUESTLIST INSTEM 'conflist' TITLE "Select conference" SIZEGADGET'
  143.             if rc > 5 then call displayerror(rc, 'REQUESTLIST:\n'THOR.LASTERROR)
  144.             else if rc ~= 5 then globalcfg.CONFERENCE = result
  145.         end
  146.         drop conflist.
  147.     end
  148.  
  149.     when choice = 2 then do
  150.         if reqnotify('Sort outgoing messages?\n\nWhen you send messages, a copy will by default be added\nto the database. If this option is active then messages\nthat do not match any triggers will be written to a\nconfigurable conference.') then do
  151.             address(bbsread)
  152.             'GETCONFLIST BBSNAME "'globalcfg.SYSTEM'" STEM 'conflist
  153.             if rc > 0 then call displayerror(rc, 'GETCONFLIST:\n'BBSREAD.LASTERROR)
  154.             address(thorport)
  155.             menulist.1 = 'Create new conference             '
  156.             menulist.2 = ''
  157.             do i = 1 to conflist.count
  158.                 mcnt = i + 2; menulist.mcnt = center(i'.', 4) || conflist.i
  159.             end
  160.             menulist.count = i + 1
  161.             confchoice = showmenu('Select destination conference')
  162.  
  163.             if confchoice = 1 then do
  164.                 call reqstring('Destination conference', 'Enter new conference name.\n\nThe new conference will be created\nwhen the configuration is saved.', 'Sent mail', 64, 1)
  165.                 if result ~= '' then globalcfg.LOCALTO = result
  166.             end
  167.             else if confchoice > 2 then do
  168.                 cno = confchoice - 2; globalcfg.LOCALTO = conflist.cno
  169.             end
  170.             drop conflist.
  171.         end
  172.         else drop globalcfg.LOCALTO
  173.     end
  174.  
  175.     when choice = 4 then call cfgtrigger(1)
  176.  
  177.     when choice = 5 then call cfgtrigger(2)
  178.  
  179.     when choice = 6 then do while movetrig ~= 0
  180.         do i = 1 to trigger.count; menulist.i = center(i'.', 4) || trigger.i.name; end
  181.         menulist.count = trigger.count
  182.         if trigger.count = 0 then do
  183.             movetrig = 0; call reqnotify('There are no triggers to rearrange.', 'Bummer')
  184.         end
  185.         else do
  186.             movetrig = showmenu('Select trigger to move, Cancel when done')
  187.             if movetrig = 0 then break
  188.  
  189.             temptrig = trigger.count + 1
  190.             if reqnotify('Move trigger up or down?', '_Up|_Down') then desttrig = movetrig - 1; else desttrig = movetrig + 1
  191.  
  192.             if (desttrig ~= 0) & (desttrig <= trigger.count) then do
  193.                 call copystem(20, movetrig, temptrig)
  194.                 call copystem(20, desttrig, movetrig)
  195.                 call copystem(20, temptrig, desttrig)
  196.             end
  197.             else call reqnotify('Can''t move trigger there.', 'Ops')
  198.         end
  199.         drop trigger.temptrig. temptrig desttrig mcnt
  200.     end
  201.  
  202.     when choice = 7 then do
  203.         do i = 1 to trigger.count; menulist.i = center(i'.', 4) || trigger.i.name; end
  204.         menulist.count = trigger.count
  205.         if trigger.count = 0 then call reqnotify('There are no triggers to delete.', 'Bummer')
  206.         else do
  207.             deltrig = showmenu('Select trigger to delete')
  208.             if deltrig = 0 then break
  209.         
  210.             if ~reqnotify('Are you sure you want to\nwant to delete this trigger:\n'trigger.deltrig.name'.') then break
  211.  
  212.             tcnt = 0
  213.             do i = 1 to trigger.count
  214.                 if i ~= deltrig then do; tcnt = tcnt + 1; if tcnt ~= i then call copystem(20, i, tcnt); end
  215.             end
  216.  
  217.             trigger.count = trigger.count - 1; i = i - 1
  218.         end
  219.         drop trigger.i. deltrig
  220.     end
  221.  
  222.     when choice = 9 then call autoconfig()
  223.  
  224.     when choice = 11 then do
  225.         call writecfg()
  226.         signal cleanup
  227.     end
  228.  
  229.     otherwise nop
  230. end
  231.  
  232. signal mainmenu
  233.  
  234. error:
  235. syntax:
  236.  
  237. select
  238.     when symbol('BBSREAD.LASTERROR') = 'VAR' then call displayerror(rc, 'Line 'sigl': 'BBSREAD.LASTERROR)
  239.     when symbol('THOR.LASTERROR') = 'VAR' then call displayerror(rc, 'Line 'sigl': 'THOR.LASTERROR)
  240.     otherwise call displayerror(30, 'Error 'rc' in line 'sigl': 'errortext(rc))
  241. end
  242.  
  243. break_c:
  244. halt:
  245. cleanup:
  246.  
  247. /*
  248. ** Close progress window if open
  249. */
  250.  
  251. if symbol('progwin') = 'VAR' then do
  252.     address(thorport)
  253.     'CLOSEPROGRESS REQ 'progwin
  254.     drop progwin
  255. end
  256.  
  257. /*
  258. ** Have a nice day
  259. */
  260.  
  261. call setclip('CSM_Active')
  262. exit(0)
  263.  
  264.  /****************************************************************************
  265. ****************************** Auto Configure(tm) *****************************
  266.  ****************************************************************************/
  267.  
  268.  
  269. autoconfig: interpret 'procedure expose 'globals
  270.             parse arg cfgtype
  271.  
  272. found = 0; foundsrch = 0; foundmsg = 0; isdigest = 0
  273. newtrig = trigger.count + 1
  274.  
  275. address(thorport)
  276. if ~(reqnotify('Auto configure.\n\nThis option will examine the message currently\ndisplayed in the Thor main window and try to\ncreate a new trigger based on the contents of\nthe message.\n\nCurrently it recognizes Aminet updates and\nsingle and digest messages from mailing lists.', 'Continue|Abort')) then return(0)
  277.  
  278. if (cursys.CONFNAME = '') then do
  279.     call reqnotify('You must enter a system and make sure a\nmessage is displayed in Thor''s main\nwindow before using auto configure.', 'Abort')
  280.     return(0)
  281. end
  282.  
  283. 'CURRENTMSG STEM 'curmsg
  284. if (rc = 30) & (THOR.LASTERROR = 'No current message.') then do
  285.     call reqnotify('You must enter a system and make sure a\nmessage is displayed in Thor''s main\nwindow before using auto configure.', 'Abort')
  286.     drop THOR.LASTERROR
  287.     return(0)
  288. end
  289. if (rc ~= 0) then call displayerror(30, 'CURRENTSYSTEM: 'THOR.LASTERROR)
  290.  
  291. if (curmsg.CONFNAME ~= 'EMail') then do
  292.     call reqnotify('Auto config requires that the message you\nwant to examine is in the EMail conference.', 'Abort')
  293.     return(0)
  294. end
  295.  
  296. address(bbsread)
  297. 'READBRMESSAGE "'curmsg.BBSNAME'" "'curmsg.CONFNAME'" 'curmsg.MSGNR' HEADSTEM 'head' TEXTSTEM 'text
  298. if (rc ~= 0) then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR)
  299. address(thorport)
  300.  
  301.  
  302. /*
  303. ** Aminet daily and weekly mailings
  304. */
  305.  
  306. if (head.TOADDR = 'aminet-daily@wugate.wustl.edu') | (head.TOADDR = 'aminet-weekly@wugate.wustl.edu') then do
  307.     found = 1
  308.     if ~(reqnotify('Recognized message as an Aminet file index update.\n\nDo you want to add a trigger for adding the list\nof files in these messages to the file database?')) then return(0)
  309.  
  310.     trigger.newtrig.name                = 'Aminet update'
  311.     trigger.newtrig.groups              = 'EMail'
  312.     trigger.newtrig.delmsg              = 1
  313.     trigger.newtrig.deluser             = 1
  314.     trigger.newtrig.matchall            = 1
  315.     trigger.newtrig.nolocal             = 0
  316.     trigger.newtrig.search.count        = 1
  317.     trigger.newtrig.search.1.type       = 'TOADDR'
  318.     trigger.newtrig.search.1.criteria   = head.TOADDR
  319.     trigger.newtrig.search.1.not        = 0
  320.     trigger.newtrig.action.count        = 1
  321.     trigger.newtrig.action.1.type       = 'RECENT'
  322.     trigger.newtrig.action.1.dontadd    = 0
  323.     trigger.newtrig.action.1.checkdupes = 0
  324.     trigger.newtrig.action.1.nostats    = 0
  325.     trigger.count = trigger.count + 1
  326.     signal acfini
  327. end
  328.  
  329. /*
  330. ** Mailing lists
  331. */
  332.  
  333. if ~(found) then do
  334.     if (symbol('text.COMMENT.COUNT') = 'VAR') then if (text.COMMENT.COUNT > 0) then do i = 1 to text.COMMENT.COUNT while foundsrch = 0
  335.  
  336.         if (upper(subword(text.COMMENT.i, 1, 1)) = 'RETURN-PATH:') then if (index(upper(subword(compress(text.COMMENT.i, '<'), 2, 1)), 'OWNER') > 0) | (index(upper(subword(compress(text.COMMENT.i, '<'), 2, 1)), 'REQUEST') > 0) | (index(upper(subword(compress(text.COMMENT.i, '<'), 2, 1)), 'BOUNCE') > 0) then do
  337.             /* Found "owner", "bounce" or "request" in Return-Path: header */
  338.  
  339.             if ~(foundsrch) & (text.REPLYADDR = 'VAR') then do
  340.                 /* Message had an reply address */
  341.                 trigger.newtrig.search.count       = 3
  342.                 trigger.newtrig.search.1.type      = 'REPLYTO'
  343.                 trigger.newtrig.search.1.criteria  = text.REPLYADDR
  344.                 trigger.newtrig.search.1.not       = 0
  345.                 trigger.newtrig.search.2.type      = 'TOADDR'
  346.                 trigger.newtrig.search.2.criteria  = text.REPLYADDR
  347.                 trigger.newtrig.search.2.not       = 0
  348.                 trigger.newtrig.search.3.type      = 'HEADER'
  349.                 trigger.newtrig.search.3.keyword   = 'Cc:'
  350.                 trigger.newtrig.search.3.criteria  = text.REPLYADDR
  351.                 trigger.newtrig.search.3.not       = 0
  352.                 if (symbol('text.REPLYADDR') = 'VAR') then trigger.newtrig.action.1.replyaddr = text.REPLYADDR
  353.                 foundsrch = 1
  354.             end
  355.             else if ~(foundsrch) then do
  356.                 call parseaddr(1); foundaddr = ''
  357.                 do j = 1 to addrs.count while foundaddr = ''
  358.                     if (index(addrs.j.addr, '@') > 0) then if (index(upper(compress(left(subword(text.COMMENT.i, 2, 1), index(subword(text.COMMENT.i, 2, 1), '@') - 1), '<>()')), upper(left(addrs.j.addr, index(addrs.j.addr, '@') - 1))) > 0) & (index(upper(compress(substr(subword(text.COMMENT.i, 2, 1), index(subword(text.COMMENT.i, 2, 1), '@') + 1), '<>()')), upper(substr(addrs.j.addr, index(addrs.j.addr, '@') + 1))) > 0) then foundaddr = addrs.j.addr
  359.                 end
  360.  
  361.                 if (foundaddr ~= '') then do
  362.                     /* To address was similar to return path address */
  363.                     trigger.newtrig.search.count       = 2
  364.                     trigger.newtrig.search.1.type      = 'TOADDR'
  365.                     trigger.newtrig.search.1.criteria  = foundaddr
  366.                     trigger.newtrig.search.1.not       = 0
  367.                     trigger.newtrig.search.2.type      = 'HEADER'
  368.                     trigger.newtrig.search.2.keyword   = 'Cc:'
  369.                     trigger.newtrig.search.2.criteria  = foundaddr
  370.                     trigger.newtrig.search.2.not       = 0
  371.                     trigger.newtrig.action.1.replyaddr = foundaddr
  372.                     foundsrch = 1
  373.                 end
  374.                 drop foundaddr
  375.             end
  376.             else foundmsg = 1
  377.         end
  378.  
  379.         if (upper(subword(text.COMMENT.i, 1, 1)) = 'ERRORS-TO:') | ((upper(subword(text.COMMENT.i, 1, 1)) = 'PRECEDENCE:') & ((upper(subword(text.COMMENT.i, 2, 1)) = 'BULK') | (upper(subword(text.COMMENT.i, 2, 1)) = 'LIST'))) then do
  380.             /* Message had an Errors-To: header or
  381.                            a Precedence: header specifying bulk or list */
  382.  
  383.             /* Check reply address */
  384.             if ~(foundsrch) then if (symbol('text.REPLYADDR') = 'VAR') then do
  385.                 trigger.newtrig.search.count       = 3
  386.                 trigger.newtrig.search.1.type      = 'REPLYTO'
  387.                 trigger.newtrig.search.1.criteria  = text.REPLYADDR
  388.                 trigger.newtrig.search.1.not       = 0
  389.                 trigger.newtrig.search.2.type      = 'TOADDR'
  390.                 trigger.newtrig.search.2.criteria  = head.TOADDR
  391.                 trigger.newtrig.search.2.not       = 0
  392.                 trigger.newtrig.search.3.type      = 'HEADER'
  393.                 trigger.newtrig.search.3.keyword   = 'Cc:'
  394.                 trigger.newtrig.search.3.criteria  = head.TOADDR
  395.                 trigger.newtrig.search.3.not       = 0
  396.                 trigger.newtrig.action.1.replyaddr = text.REPLYADDR
  397.                 foundsrch = 1
  398.             end
  399.             else foundmsg = 1
  400.         end
  401.  
  402.     end
  403.  
  404.     if (foundsrch | foundmsg) then do
  405.         /* See if it is a digest message of some kind */
  406.  
  407.         if (symbol('text.PART.COUNT') = 'VAR') & (text.PART.COUNT > 0) then do
  408.             call reqnotify('This message contains more than one message part.\n\nIn order to set up a correct trigger I will have\nto know if this message is a digest message or a\nsingle, ordinary mailing list message.\n\nDigest messages are collections of messages posted\nto mailing list and are usually sent out once a\nday. SortMail can split digests into seperate\nmessages for easier reading.', 'Digest|_Single|Abort')
  409.             if (result = 0) then return(0)
  410.             if (result = 1) then isdigest = 1
  411.         end
  412.  
  413.     end
  414.  
  415.     if (foundsrch | foundmsg) & (symbol('trigger.newtrig.action.1.replyaddr') ~= 'VAR') then do
  416.         call parseaddr(1)
  417.  
  418.         mcnt = 0
  419.         do i = 1 to addrs.count
  420.             if (addrs.i.addr = '') then iterate i
  421.             mcnt = mcnt + 1
  422.             menulist.mcnt = addrs.i.name || ' <' || addrs.i.addr || '>'
  423.         end
  424.         menulist.count = mcnt
  425.  
  426.         if (menulist.count = 0) then do
  427.             call reqnotify('I could not determine the address used for\nposting messages on the mailing list, nor\ncould I find any addresses used in this\nmessage.  You will have to configure a\ntrigger for this mailing list yourself.', 'Ok')
  428.             return(0)
  429.         end
  430.  
  431.         call reqnotify('I could not determine the address used for\nposting messages on the mailing list.\n\nNext a list of addresses given in this\nmessage will appear. Select the correct\none or, if you do not find it, cancel the\nrequester.', 'Ok')
  432.  
  433.         choice = showmenu('Select list address')
  434.         if (choice = 0) then return(0)
  435.         trigger.newtrig.action.1.replyaddr = addrs.choice.addr; foundsrch = 1
  436.     end
  437.  
  438.     if (foundsrch) then do
  439.         trigger.newtrig.groups              = 'EMail'
  440.         trigger.newtrig.delmsg              = 1
  441.         trigger.newtrig.deluser             = 1
  442.         trigger.newtrig.matchall            = 0
  443.         trigger.newtrig.nolocal             = 1
  444.         trigger.newtrig.action.count        = 1
  445.         if ~(isdigest) then trigger.newtrig.action.1.type = 'COPY'
  446.         else trigger.newtrig.action.1.type = 'SPLITDIGEST'
  447.             
  448.         call reqstring('Auto config', 'You can now create a trigger for moving messages\nposted to this mailing list to another conference.\n\nThe trigger name could for example be the address\nof the mailing list, which is what is suggested\nbelow.  The name for the trigger will only be\nused to identify this particular trigger in\nCfgSortMail and the log.\n\nHit cancel if you do not want to create a new\ntrigger.', trigger.newtrig.action.1.replyaddr, 64, 1)
  449.         if (result = '') then do; found = 0; foundsrch = 0; drop trigger.newtrig.; signal mlfini; end
  450.         else trigger.newtrig.name = result
  451.  
  452.         call reqstring('Auto config','Enter a name for the conference messages\nbelonging to this mailing list should be\ncopied to.', '(ML) ', 64, 1)
  453.         if (result = '') then do; found = 0; foundsrch = 0; drop trigger.newtrig.; signal mlfini; end
  454.         else trigger.newtrig.action.1.destconf = result
  455.  
  456.         found = 1; trigger.count = newtrig
  457.     end
  458. end
  459.  
  460. mlfini:
  461.  
  462. acfini:
  463.  
  464. if ~(found) then call reqnotify('This message did not contain any elements\nCfgSortMail recognized. A trigger will\nhave to be configured manually.', 'Ok')
  465. else call reqnotify('The new trigger has been created. Please verify\nthat the information is correct by selecting\n"Edit trigger" in the main menu and then\n' || newtrig || '. ' || trigger.newtrig.name, 'Ok')
  466.  
  467. drop found
  468.  
  469. return(0)
  470.  
  471.  
  472.  /****************************************************************************
  473. *************** Put addresses and names in a string into a stem ***************
  474.  ****************************************************************************/
  475.  
  476. parseaddr: interpret 'procedure expose 'globals
  477.            parse arg checkcc
  478.  
  479. i = 1; acnt = 0; usedhead = 0; drop addrs.
  480.  
  481. if (symbol('head.TOADDR') = 'VAR') & ~(index(head.TOADDR, ',') > 0) then do
  482.     acnt = acnt + 1; addrs.acnt.name = ''; addrs.acnt.cc = 0; usedhead = 1
  483.     addrs.acnt.addr = head.TOADDR
  484.     if (symbol('head.TONAME') = 'VAR') then addrs.acnt.name = head.TONAME
  485. end
  486.  
  487. if (symbol('text.COMMENT.COUNT') = 'VAR') then if (text.COMMENT.COUNT > 0) then do while i <= text.COMMENT.COUNT
  488.     thiscc = 0
  489.  
  490.     if (checkcc = 1) & (upper(subword(text.COMMENT.i, 1, 1)) = 'CC:') then thiscc = 1
  491.  
  492.     if (thiscc) | (upper(subword(text.COMMENT.i, 1, 1)) = 'TO:') then do
  493.         addrs = subword(text.COMMENT.i, 2)
  494.         do forever
  495.             addrs = strip(addrs, 'B', ' ' || '09'x)
  496.  
  497.             offset = 1
  498.             do forever
  499.                 length = index(substr(addrs, offset), ','); if (length = 0) then length = length(addrs) - offset + 1
  500.                 thisaddr = strip(substr(addrs, offset, length), 'B', ', ');
  501.                 acnt = acnt + 1; addrs.acnt.addr = ''; addrs.acnt.name = ''
  502.                 if (thiscc) then addrs.acnt.cc = 1; else addrs.acnt.cc = 0
  503.  
  504.                 if (words(thisaddr) = 1) then addrs.acnt.addr = strip(thisaddr, 'B', '<>()')
  505.                 else if (index(thisaddr, '<') > 0) then do
  506.                     addrstart  = index(thisaddr, '<')
  507.                     addrlength = index(substr(thisaddr, addrstart), '>')
  508.                     addrs.acnt.addr = strip(substr(thisaddr, addrstart + 1, addrlength), 'B', '> ')
  509.                     addrs.acnt.name = strip(delstr(thisaddr, addrstart, addrlength), 'B', ' "' || '27'x)
  510.                 end
  511.                 else do j = 1 to words(thisaddr)
  512.                     thispart = strip(subword(thisaddr, j, 1), 'B', '<>" ' || '27'x)
  513.                     if (index(thispart, '@') > 0) then addrs.acnt.addr = thispart
  514.                     else addrs.acnt.name = addrs.acnt.name || thispart || ' '
  515.                 end
  516.  
  517.                 if ~(thiscc) & (usedhead) & (addrs.acnt.addr = addrs.1.addr) & (addrs.acnt.name = addrs.1.name) then do
  518.                     drop addrs.acnt.; acnt = acnt - 1
  519.                 end
  520.  
  521.                 if (offset + length >= length(addrs)) then break
  522.                 offset = offset + length
  523.             end
  524.  
  525.             j = i + 1; if ~((c2d(left(text.COMMENT.j, 1)) = 9) | (c2d(left(text.COMMENT.j, 1)) = 32)) then break
  526.             i = i + 1; addrs = text.COMMENT.i
  527.         end
  528.     end
  529.     i = i + 1
  530. end
  531.  
  532. addrs.COUNT = acnt
  533.  
  534. return(0)
  535.  
  536.  
  537.  /****************************************************************************
  538. ****************************** Configure trigger ******************************
  539.  ****************************************************************************/
  540.  
  541. cfgtrigger: interpret 'procedure expose 'globals
  542.             parse arg cfgtype
  543.  
  544. if cfgtype = 1 then do
  545.     /* Add trigger */
  546.     temp.name         = 'Change me'
  547.     temp.groups       = 'EMail'
  548.     temp.delmsg       = 0    
  549.     temp.deluser      = 1
  550.     temp.matchall     = 1
  551.     temp.nolocal      = 0
  552.     temp.action.count = 0
  553.     temp.search.count = 0
  554.  
  555.     org = trigger.count + 1
  556. end
  557.  
  558. if cfgtype = 2 then do
  559.     /* Edit trigger */
  560.     if trigger.count = 0 then do
  561.         call reqnotify('There are no triggers to configure.', 'Bummer')
  562.         return(0)
  563.     end
  564.  
  565.     do i = 1 to trigger.count; menulist.i = center(i'.', 4) || trigger.i.name; end
  566.     menulist.count = trigger.count
  567.     org = showmenu('Select trigger to edit')
  568.     if org = 0 then return(0)
  569.  
  570.     call copystem(1, org)
  571. end
  572.  
  573. triggermenu:
  574.  
  575. menulist.1  = 'Name:                  'temp.name
  576. menulist.2  = 'Search in:             'temp.groups
  577. menulist.3  = ''
  578. menulist.4  = 'Leave in EMail:        '; if temp.DELMSG then menulist.4 = menulist.4 || 'NO'; else menulist.4 = menulist.4 || 'YES'
  579. menulist.5  = 'Add outgoing messages: '; if temp.NOLOCAL then menulist.5 = menulist.5 || 'NO'; else menulist.5 = menulist.5 || 'YES'
  580. menulist.6  = 'Add user:              '; if ~temp.deluser  then menulist.6 = menulist.6 || 'YES'; else menulist.6 = menulist.6 || 'NO'
  581. menulist.7  = 'Match all criterias:   '; if temp.matchall then menulist.7 = menulist.7 || 'YES'; else menulist.7 = menulist.7 || 'NO'
  582. menulist.8  = ''
  583. menulist.9  = 'Add search entry'
  584. menulist.10 = 'Add action entry'
  585. menulist.11 = ''
  586. menulist.12 = 'Search entries:'; sline = 12
  587. if temp.search.count > 0 then do i = 1 to temp.search.count
  588.     lno = sline + i
  589.     if (symbol('temp.search.i.pattern') = 'VAR') then do
  590.         menulist.lno = 'Pattern'; crit = temp.search.i.pattern
  591.     end
  592.     else do
  593.         menulist.lno = 'String '; crit = temp.search.i.criteria
  594.     end
  595.     if (temp.search.i.not) then menulist.lno = menulist.lno || ' NOT'; else menulist.lno = menulist.lno || '    '
  596.     select
  597.         when temp.search.i.type = 'TOADDR'   then menulist.lno = menulist.lno || ' in to address:   'crit
  598.         when temp.search.i.type = 'TONAME'   then menulist.lno = menulist.lno || ' in to name:      'crit
  599.         when temp.search.i.type = 'FROMADDR' then menulist.lno = menulist.lno || ' in from address: 'crit
  600.         when temp.search.i.type = 'FROMNAME' then menulist.lno = menulist.lno || ' in from name:    'crit
  601.         when temp.search.i.type = 'SUBJECT'  then menulist.lno = menulist.lno || ' in subject:      'crit
  602.         when temp.search.i.type = 'REPLYTO'  then menulist.lno = menulist.lno || ' in Reply-To:     'crit
  603.         when temp.search.i.type = 'BODY'     then menulist.lno = menulist.lno || ' in message body: 'crit
  604.         when temp.search.i.type = 'HEADER'   then menulist.lno = menulist.lno || ' in header field: "'temp.search.i.keyword'"-line: 'crit
  605.         otherwise menulist.lno = menulist.lno || ' in unknown:      'crit
  606.     end
  607.     drop crit
  608. end
  609. else do; lno = sline + 1; menulist.lno = ' None'; end
  610. lno = lno + 1; menulist.lno  = ''
  611. lno = lno + 1; menulist.lno  = 'Action entries:'; aline = lno
  612. if temp.action.count > 0 then do i = 1 to temp.action.count
  613.     lno = aline + i
  614.     select
  615.         when temp.action.i.type = 'COPY'        then do; menulist.lno = ' Copy message:           To "'; if symbol('temp.action.i.destsys') = 'VAR' then menulist.lno = menulist.lno||temp.action.i.destsys': '; menulist.lno = menulist.lno||temp.action.i.destconf'"'; if symbol('temp.action.i.replyaddr') = 'VAR' then menulist.lno = menulist.lno', ReplyAddr "'temp.action.i.replyaddr'"'; end
  616.         when temp.action.i.type = 'RECENT'      then do; menulist.lno = ' AmiNet RECENT:          '; if temp.action.i.dontadd = 1 then menulist.lno = menulist.lno'Don''t add to database'; else do; menulist.lno = menulist.lno'Add to database, '; if temp.action.i.checkdupes = 1 then menulist.lno = menulist.lno'check for dupes. '; else menulist.lno = menulist.lno'don''t check for dupes. '; end; if temp.action.i.nostats = 1 then menulist.lno = menulist.lno'No MOTD & statistics'; else menulist.lno = menulist.lno'Show MOTD & statistics'; end
  617.         when temp.action.i.type = 'SAVEMESSAGE' then do; menulist.lno = ' Save message:           '; if symbol('temp.action.i.filename') = 'VAR' then menulist.lno = menulist.lno'Filename "'temp.action.i.filename'"'; else menulist.lno = menulist.lno'Directory "'temp.action.i.directory'"'; end
  618.         when temp.action.i.type = 'SPLITDIGEST' then do; menulist.lno = ' Split digest:           To "'temp.action.i.destconf'"'; if symbol('temp.action.i.replyaddr') = 'VAR' then menulist.lno = menulist.lno', ReplyAddr "'temp.action.i.replyaddr'"'; end
  619.         when temp.action.i.type = 'FORWARD'     then do; menulist.lno = ' Forward message:        To "'temp.action.i.sendto'" in "'temp.action.i.destconf'"'; if (symbol('temp.action.i.textfile') = 'VAR') then menulist.lno = menulist.lno || ', File "'temp.action.i.textfile'"'; end
  620.         when temp.action.i.type = 'MAIL'        then do; menulist.lno = ' Mail text file:         '; if (symbol('temp.action.i.sendto') = 'VAR') then menuist.lno = menulist.lno || 'To "'temp.action.i.sendto'" in '; else menulist.lno = menulist.lno || 'In '; menulist.lno = menulist.lno || '"'temp.action.i.destconf'", File "'temp.action.i.textfile'"'; end
  621.         when temp.action.i.type = 'EXTERNAL'    then do; menulist.lno = ' Ext. script:            Script name "'temp.action.i.scriptname'"'; if symbol('temp.action.i.scriptopts') = 'VAR' then menulist.lno = menulist.lno', Options "'temp.action.i.scriptopts'"'; end
  622.         otherwise menulist.lno = ' Unknown:                'temp.action.i.type
  623.     end
  624.     menulist.lno = menulist.lno
  625. end
  626. else do; lno = aline + 1; menulist.lno = ' None'; end
  627. lno = lno + 1; menulist.lno  = ''
  628. lno = lno + 1; menulist.lno  = 'Accept and return'
  629. menulist.count = lno
  630.  
  631. choice = showmenu('Trigger configuration', 8)
  632.  
  633. select
  634.     when choice = 0 then do
  635.         if reqnotify('Are you sure you want to abort\nconfiguration of this trigger?') then do; drop temp.; return(0); end
  636.     end
  637.  
  638.     when choice = 1 then temp.name = reqstring('Trigger name', 'Enter a name for the trigger.', addasterix(temp.name), 64)
  639.  
  640.     when choice = 2 then temp.groups = reqstring('Search in', 'Enter the conference names to search\nin. If you want to search more than\none group, use an AmigaDOS pattern\nto limit your search.', addasterix(temp.groups), 256)
  641.  
  642.     when choice = 4 then do
  643.         answer = reqnotify('Leave in EMail?\n\nIf you want a copy of messages processed by this\ntrigger to be left in the EMail conference, select "Yes".')
  644.         if answer = 0 then temp.DELMSG = 1; else temp.DELMSG = 0
  645.     end
  646.  
  647.     when choice = 5 then do
  648.         answer = reqnotify('Add outgoing messages?\n\nBy default a local copy will be added to the\ndatabase when a message is sent. By selecting\n"No" local copies that match this trigger will\nnot be added to the database, nor will any of\nthe actions specified in this trigger be\nperformed on them.')
  649.         if answer = 0 then temp.NOLOCAL = 1; else temp.NOLOCAL = 0
  650.     end
  651.  
  652.     when choice = 6 then do
  653.         answer = reqnotify('Add user?\n\nDo you want users sending mail processed by this\ntrigger to be added to the user database?')
  654.         if answer = 0 then temp.DELUSER = 1; else temp.DELUSER = 0
  655.     end
  656.  
  657.     when choice = 7 then temp.MATCHALL = reqnotify('Match all criterias?\n\nIf you select yes, then all the configured search\ncriterias must be fulfilled for a message to be\nprocessed.  Otherwise only one criteria will do.')
  658.  
  659.     when choice = 9 then call cfgsearch(1)
  660.  
  661.     when choice = 10 then call cfgaction(1)
  662.  
  663.     when (choice = sline) | (choice = aline) then call reqnotify('Select an entry to\nedit or delete it.', 'I''ll do that')
  664.  
  665.     when (choice > sline) & (choice < (aline - 1)) & (temp.search.count > 0) then do
  666.         call reqnotify('Do you want to edit or\ndelete this search entry?', 'Edit|_Delete|_Abort')
  667.         if result = 1 then call cfgsearch(2, (choice - sline))
  668.         else if result = 2 then call delsearch(choice - sline)
  669.     end
  670.  
  671.     when (choice > aline) & (choice < (lno - 1)) & (temp.action.count > 0) then do
  672.         call reqnotify('Do you want to edit or\n delete this action entry?', 'Edit|_Delete|_Abort')
  673.         if result = 1 then call cfgaction(2, (choice - aline))
  674.         else if result = 2 then call delaction(choice - aline)
  675.     end
  676.  
  677.     when choice = lno then do
  678.         if cfgtype = 1 then do; trigger.count = trigger.count + 1; org = trigger.count; end
  679.         call copystem(10, , org); drop temp.
  680.         return(0)
  681.     end
  682.  
  683.     otherwise nop
  684. end
  685.  
  686. signal triggermenu
  687.  
  688. return(0)
  689.  
  690.  
  691.  /****************************************************************************
  692. **************************** Configure action entry ***************************
  693.  ****************************************************************************/
  694.  
  695. cfgaction: interpret 'procedure expose 'globals
  696.            parse arg atype, ano
  697.  
  698. aborted = 0
  699.  
  700. if atype = 1 then do
  701.     menulist.1 = 'Copy message to another conference'
  702.     menulist.2 = 'Parse AmiNet RECENT message'
  703.     menulist.3 = 'Save message to disk'
  704.     menulist.4 = 'Split digest'
  705.     menulist.5 = 'Forward message'
  706.     menulist.6 = 'Mail text file'
  707.     menulist.7 = 'Run an external ARexx script'
  708.     menulist.count = 7
  709.     act = showmenu('Select action type', 10)
  710.  
  711.     select
  712.         when act = 0 then return(0)
  713.         when act = 1 then do; atemp.type = 'COPY'; atemp.destconf = ''; end
  714.         when act = 2 then do; atemp.type = 'RECENT'; atemp.checkdupes = 0; atemp.dontadd = 0; atemp.nostats = 0; end
  715.         when act = 3 then do; atemp.type = 'SAVEMESSAGE'; atemp.header = 0; atemp.append = 0; atemp.nobin = 0; end
  716.         when act = 4 then do; atemp.type = 'SPLITDIGEST'; atemp.destconf = ''; end
  717.         when act = 5 then do; atemp.type = 'FORWARD'; atemp.destconf = ''; atemp.sendto = ''; end
  718.         when act = 6 then do; atemp.type = 'MAIL'; atemp.destconf = ''; atemp.sendto = ''; atemp.textfie = ''; end
  719.         when act = 7 then do; atemp.type = 'EXTERNAL'; atemp.scriptname = ''; end
  720.         otherwise nop
  721.     end
  722.     drop act
  723. end
  724.     
  725. if atype = 2 then call copystem(2, ano)
  726.  
  727. select
  728.     when atemp.type = 'COPY' then do
  729.         syschoice = getsystem()
  730.         if (syschoice ~= 0) then do
  731.             if (bbslist.syschoice ~= globalcfg.SYSTEM) then atemp.destsys = bbslist.syschoice
  732.             else atemp.destsys = globalcfg.SYSTEM
  733.  
  734.             call getconference(atemp.destsys, 1)
  735.             if (result ~= 0) then do
  736.                 if symbol('atemp.replyaddr') ~= 'VAR' then atemp.replyaddr = ''
  737.                 atemp.replyaddr = reqstring('Reply address', 'Enter an address replies should be addressed to.\nA reply address is required if you use Local2Email.\nIf no reply address is given then replies will\nbe directed to the author or the address given\nin the Reply-To: header field.  To do so, cancel\nthis requester.\n', addasterix(atemp.replyaddr), 64, 1, 'replyaddr')
  738.                 if atemp.replyaddr = '' then drop atemp.replyaddr
  739.             end
  740.             else aborted = 1
  741.         end
  742.         else aborted = 1
  743.  
  744.     end
  745.  
  746.     when atemp.type = 'RECENT' then do
  747.         call reqnotify('Do you want to add new files to the file database?\nIf you select no, the new files will only be shown\nin the New Files window in Thor.')
  748.         if result = 1 then atemp.DONTADD = 0; else atemp.DONTADD = 1
  749.  
  750.         if ~atemp.DONTADD then atemp.CHECKDUPES = reqnotify('Do you want to check for duplicates\nwhen adding files to the file database?\nThis will slow down operation.')
  751.         else drop atemp.CHECKDUPES
  752.  
  753.         call reqnotify('Sometimes a message of the day and statistics\nare added to the listings.  Do you want to display\nthese?  Some of them may contain valuable info.')
  754.         if result = 1 then atemp.NOSTATS = 0; else atemp.NOSTATS = 1
  755.     end
  756.  
  757.      when atemp.type = 'SAVEMESSAGE' then do
  758.         if reqnotify('Do you want to save to a specific\nfile or to a directory using the\nsubject as the file name?', 'File|_Directory') then do; drop atemp.directory; if symbol('atemp.filename') ~= 'VAR' then atemp.filename = ''; end
  759.         else do; drop atemp.filename; if symbol('atemp.directory') ~= 'VAR' then atemp.directory = ''; end
  760.  
  761.         if symbol('atemp.filename') = 'VAR' then do
  762.             pathpart = getpathpart(atemp.filename)
  763.             filepart = getfilepart(atemp.filename)
  764.             pattern = ''
  765.         end
  766.         else do
  767.             pathpart = getpathpart(atemp.directory)
  768.             filepart = ''
  769.             pattern = '~(#?)'
  770.         end
  771.  
  772.         call reqfile('Select path', pathpart, filepart, pattern)
  773.         if (result ~= '') then do
  774.             if symbol('atemp.filename') = 'VAR' then atemp.filename = result
  775.             else do
  776.                 atemp.directory = result
  777.                 if right(atemp.directory, 1) ~= '/' & right(atemp.directory, 1) ~= ':' then atemp.directory = atemp.directory || '/'
  778.             end
  779.  
  780.             if symbol('atemp.directory') = 'VAR' then do
  781.                 if reqnotify('Do you want to substitute part of the\nsubject with another string?  This can\nbe done to shorten the filename of\nsaved messages in order to fit into\nAmigaDOS''s 35 character filename limit.') then do
  782.                     if symbol('atemp.substitute') ~= 'VAR' then atemp.substitute = ''
  783.                     atemp.substitute = reqstring('Substitute string', 'Enter the string you want to substitute.', addasterix(atemp.substitute), 256, 1)
  784.                     if atemp.substitute = '' then drop atemp.substitute
  785.                     else do
  786.                         if symbol('atemp.with') ~= 'VAR' then atemp.with = ''
  787.                         atemp.with = reqstring('Substitute with', 'Enter the string you want to substitute with.', addasterix(atemp.with), 256, 1)
  788.                         if atemp.with = '' then drop atemp.substitute atemp.with
  789.                     end
  790.                 end
  791.                 else drop atemp.substitute atemp.with
  792.             end
  793.              else drop atemp.substitute atemp.with
  794.  
  795.             atemp.HEADER = reqnotify('Include header?\n\nDo you want to include\nheaders in the saved messages?')
  796.  
  797.             atemp.APPEND = reqnotify('Append to file?\n\nDo you want to append messages if the\ndestination file already exists, then\nselect "YES".  Otherwise the file\nwill be overwritten.')
  798.  
  799.             if reqnotify('Save binaries?\n\nDo you want binary file parts to be copied\nto the download directory, then select "YES".\nOtherwise no action will be taken when a file\npart is found.') then atemp.nobin = 0; else atemp.nobin = 1
  800.         end
  801.         else aborted = 1
  802.     end
  803.  
  804.     when atemp.type = 'SPLITDIGEST' then do
  805.         syschoice = getsystem()
  806.         if (syschoice ~= 0) then do
  807.             if bbslist.syschoice ~= globalcfg.SYSTEM then atemp.destsys = bbslist.syschoice
  808.             else atemp.destsys = globalcfg.SYSTEM
  809.  
  810.             call getconference(atemp.destsys, 1)
  811.             if (result ~= 0) then do
  812.                 if symbol('atemp.replyaddr') ~= 'VAR' then atemp.replyaddr = ''
  813.                 atemp.replyaddr = reqstring('Reply address', 'Enter an address replies should be addressed to.\n\nIf no reply address is given then replies will\nbe directed to the author or the address given\nin the Reply-To: header field.  To do so, cancel\nthis requester.', addasterix(atemp.replyaddr), 64, 1, 'replyaddr')
  814.                 if atemp.replyaddr = '' then drop atemp.replyaddr
  815.             end
  816.             else aborted = 1
  817.         end
  818.         else aborted = 1
  819.     end
  820.  
  821.     when atemp.type = 'FORWARD' then do
  822.         call getconference(globalcfg.SYSTEM, 0)
  823.         if (result ~= 0) then do
  824.             if (symbol('atemp.sendto') ~= 'VAR') then atemp.sendto = ''
  825.             atemp.sendto = reqstring('Send to', 'Enter an address the messages should\nbe forwarded to. (required)', addasterix(atemp.sendto), 64, 0)
  826.             if (atemp.sendto ~= '') then do
  827.  
  828.                 if (symbol('atemp.textfile') = 'VAR') then do
  829.                     pathpart = getpathpart(atemp.textfile)
  830.                     filepart = getfilepart(atemp.textfile)
  831.                 end
  832.                 else do
  833.                     pathpart = ''
  834.                     filepart = ''
  835.                 end
  836.  
  837.                 call reqfile('Select text file to attach, cancel for none', pathpart, filepart, '#?')
  838.                 if (result ~= '' & ~(right(result, 1) = '/' | right(result, 1) = ':')) then atemp.textfile = result; else drop atemp.textfile
  839.             end
  840.             else aborted = 1
  841.         end
  842.         else aborted = 1
  843.     end
  844.  
  845.     when atemp.type = 'MAIL' then do
  846.         call getconference(globalcfg.SYSTEM, 0)
  847.         if (result ~= 0) then do
  848.             if (symbol('atemp.subject') ~= 'VAR') then atemp.subject = ''
  849.             atemp.subject = reqstring('Subject', 'Enter a message subject to beused for the mails. (required)', addasterix(atemp.subject), 256, 0)
  850.             if (atemp.subject ~= '') then do
  851.                 if (symbol('atemp.textfile') = 'VAR') then do
  852.                     pathpart = getpathpart(atemp.textfile)
  853.                     filepart = getfilepart(atemp.textfile)
  854.                 end
  855.                 else do
  856.                     pathpart = ''
  857.                     filepart = ''
  858.                 end
  859.  
  860.                 call reqfile('Select text file (required)', pathpart, filepart, '#?')
  861.                 if (result ~= '' & ~(right(result, 1) = '/' | right(result, 1) = ':')) then do
  862.                     atemp.textfile = result
  863.  
  864.                     if (symbol('atemp.sendto') ~= 'VAR') then atemp.sendto = ''
  865.                     atemp.sendto = reqstring('Send to', 'Enter an address the messages\nshould be mailed to. If you do\nnot specify an address it will\nbe mailed to the sender of the\nmessage that caused the trigger.', addasterix(atemp.sendto), 64, 0)
  866.                     if atemp.sendto = '' then drop atemp.sendto
  867.                 end
  868.                 else aborted = 1
  869.             end
  870.             else aborted = 1
  871.         end
  872.         else aborted = 1
  873.     end
  874.  
  875.     when atemp.type = 'EXTERNAL' then do
  876.         if symbol('atemp.scriptname') ~= 'VAR' then atemp.scriptname = ''
  877.         filepart = getfilepart(atemp.scriptname)
  878.         pathpart = getpathpart(atemp.scriptname); if pathpart = '' then pathpart = thorpath'rexx'
  879.  
  880.         call reqfile('Select script', pathpart, filepart, '(#?.br|#?.thor|#?.rexx)')
  881.         if (result ~= '') then do
  882.             atemp.scriptname = result
  883.  
  884.             if symbol('atemp.scriptopts') ~= 'VAR' then atemp.scriptopts = ''
  885.             atemp.scriptopts = reqstring('Script options', 'Enter the command line options that should be\npassed to the script.  The following keywords\ncan be used:\n\n  %%s  returns the system name\n  %%c  returns the email conference name\n  %%n  returns the number of the message\n\nSortMail will add double quotes (") around %%s\nand %%c so don''t do that yourself, but make\nsure you seperate the items with a space.\n\nIf you don''t want to pass any options then\ncancel this requester.', addasterix(atemp.scriptopts), 256, 1)
  886.             if atemp.scriptopts = '' then drop atemp.scriptopts
  887.         end
  888.         else aborted = 1
  889.     end
  890.  
  891.     otherwise nop
  892. end
  893.  
  894. /* Update action stem */
  895.  
  896. if ~(aborted) then do
  897.     if atype = 1 then do; temp.action.count = temp.action.count + 1; ano = temp.action.count; end
  898.     call copystem(11,, ano)
  899. end
  900.  
  901. drop atemp. ano pathpart filepart syschoice conflist. menulist. cno mcnt
  902.  
  903. return(0)
  904.  
  905.  /****************************************************************************
  906. **************************** Configure search entry ***************************
  907.  ****************************************************************************/
  908.  
  909. cfgsearch: interpret 'procedure expose 'globals
  910.            parse arg stype, sno
  911.  
  912. if stype = 1 then do
  913.     menulist.1  = 'In from address      '
  914.     menulist.2  = 'Not in from address'
  915.     menulist.3  = 'In from name'
  916.     menulist.4  = 'Not in from name'
  917.     menulist.5  = 'In to address'
  918.     menulist.6  = 'Not in to address'
  919.     menulist.7  = 'In to name'
  920.     menulist.8  = 'Not in subject'
  921.     menulist.9  = 'In subject'
  922.     menulist.10 = 'Not in subject'
  923.     menulist.11 = 'In Reply-To'
  924.     menulist.12 = 'Not in Reply-To'
  925.     menulist.13 = 'In header'
  926.     menulist.14 = 'Not in header'
  927.     menulist.15 = 'In body'
  928.     menulist.16 = 'Not in body'
  929.     menulist.count = 16
  930.  
  931.     searchin = showmenu('Select search type', 18)
  932.  
  933.     select
  934.         when searchin = 0 then return(0)
  935.         when searchin = 1 then do; stemp.type = 'FROMADDR'; stemp.not = 0; end
  936.         when searchin = 2 then do; stemp.type = 'FROMADDR'; stemp.not = 1; end
  937.         when searchin = 3 then do; stemp.type = 'FROMNAME'; stemp.not = 0; end
  938.         when searchin = 4 then do; stemp.type = 'FROMNAME'; stemp.not = 1; end
  939.         when searchin = 5 then do; stemp.type = 'TOADDR'; stemp.not = 0; end
  940.         when searchin = 6 then do; stemp.type = 'TOADDR'; stemp.not = 1; end
  941.         when searchin = 7 then do; stemp.type = 'TONAME'; stemp.not = 0; end
  942.         when searchin = 8 then do; stemp.type = 'TONAME'; stemp.not = 1; end
  943.         when searchin = 9 then do; stemp.type = 'SUBJECT'; stemp.not = 0; end
  944.         when searchin = 10 then do; stemp.type = 'SUBJECT'; stemp.not = 1; end
  945.         when searchin = 11 then do; stemp.type = 'REPLYTO'; stemp.not = 0; end
  946.         when searchin = 12 then do; stemp.type = 'REPLYTO'; stemp.not = 1; end
  947.         when searchin = 13 then do; stemp.type = 'HEADER'; stemp.not = 0; end
  948.         when searchin = 14 then do; stemp.type = 'HEADER'; stemp.not = 1; end
  949.         when searchin = 15 then do; stemp.type = 'BODY'; stemp.not = 0; end
  950.         when searchin = 16 then do; stemp.type = 'BODY'; stemp.not = 1; end
  951.         otherwise nop
  952.     end
  953.  
  954.     stemp.criteria = ''
  955.     if stemp.type = 'HEADER' then stemp.keyword  = ''
  956.  
  957.     drop searchin
  958. end
  959.  
  960. if stype = 2 then call copystem(3, sno)
  961.  
  962. select
  963.     when stemp.type = 'FROMADDR'  then where = 'from address'
  964.     when stemp.type = 'FROMNAME'  then where = 'from name'
  965.     when stemp.type = 'TOADDR'    then where = 'to address'
  966.     when stemp.type = 'TONAME'    then where = 'to name'
  967.     when stemp.type = 'SUBJECT'   then where = 'subject'
  968.     when stemp.type = 'REPLYTO'   then where = 'reply-to'
  969.     when stemp.type = 'HEADER'    then where = 'header'
  970.     when stemp.type = 'BODY'      then where = 'message body'
  971.     otherwise stemp.type = '!unknown!'
  972. end
  973.  
  974. if (symbol('stemp.pattern') = 'VAR') then newsearch = stemp.pattern
  975. else newsearch = stemp.criteria
  976.  
  977. select
  978.     when stemp.type = 'HEADER' then do
  979.         stemp.keyword = addasterix(reqstring('Search keyword', 'Enter the first word on the\nheader line you want to search in.', addasterix(stemp.keyword), 64, 1, 'headline'))
  980.         if stemp.keyword = '' then signal bailout
  981.         newsearch = reqstring('Search criteria', 'Enter a string or a pattern to\nsearch for in the 'where'.', addasterix(newsearch), 64, 1, stemp.TYPE, 1)
  982.         if newsearch = '' then signal bailout
  983.         if left(newsearch, 1) = '02'x then do
  984.             stemp.pattern = substr(newsearch, 2); drop stemp.criteria
  985.         end
  986.         else do
  987.             stemp.criteria = substr(newsearch, 2); drop stemp.pattern
  988.         end
  989.     end
  990.  
  991.     when stemp.type = 'BODY' then do
  992.         newsearch = reqstring('Search criteria', 'Enter a string or a pattern to\nsearch for in the 'where'.', addasterix(newsearch), 64, 0,, 1)
  993.         if newsearch = '' then signal bailout
  994.         if left(newsearch, 1) = '02'x then do
  995.             stemp.pattern = substr(newsearch, 2); drop stemp.criteria
  996.         end
  997.         else do
  998.             stemp.criteria = substr(newsearch, 2); drop stemp.pattern
  999.         end
  1000.         drop stemp.keyword
  1001.     end
  1002.  
  1003.     otherwise do
  1004.         newsearch = reqstring('Search criteria', 'Enter a string or a pattern to\nsearch for in the 'where'.', addasterix(newsearch), 64, 1, stemp.TYPE, 1)
  1005.         if newsearch = '' then signal bailout
  1006.         if left(newsearch, 1) = '02'x then do
  1007.             stemp.pattern = substr(newsearch, 2); drop stemp.criteria
  1008.         end
  1009.         else do
  1010.             stemp.criteria = substr(newsearch, 2); drop stemp.pattern
  1011.         end
  1012.         drop stemp.keyword
  1013.     end
  1014. end
  1015.  
  1016. /* Update search stem */
  1017.  
  1018. if stype = 1 then do; temp.search.count = temp.search.count + 1; sno = temp.search.count; end
  1019. call copystem(12, , sno)
  1020.  
  1021. bailout:
  1022.  
  1023. drop stemp. newsearch
  1024. return(0)
  1025.  
  1026.  
  1027.  /****************************************************************************
  1028. ***************************** Delete action entry *****************************
  1029.  ****************************************************************************/
  1030.  
  1031. delaction: interpret 'procedure expose 'globals
  1032.            parse arg ano
  1033.  
  1034. if reqnotify('Are you sure you want\nto delete this entry?') then do
  1035.     acnt = 0
  1036.     do i = 1 to temp.action.count
  1037.         if i ~= ano then do; acnt = acnt + 1; call copystem(21, i, acnt); end
  1038.     end
  1039.     temp.action.count = temp.action.count - 1; i = i - 1
  1040.  
  1041.     drop acnt temp.action.i.
  1042. end
  1043.  
  1044. return(0)
  1045.  
  1046.  
  1047.  /****************************************************************************
  1048. ***************************** Delete search entry *****************************
  1049.  ****************************************************************************/
  1050.  
  1051. delsearch: interpret 'procedure expose 'globals
  1052.            parse arg sno
  1053.  
  1054. if reqnotify('Are you sure you want\nto delete this entry?') then do
  1055.     scnt = 0
  1056.     do i = 1 to temp.search.count
  1057.         if i ~= sno then do; scnt = scnt + 1; call copystem(22, i, scnt); end
  1058.     end
  1059.     temp.search.count = temp.search.count - 1; i = i - 1
  1060.  
  1061.     drop scnt temp.search.i.
  1062. end
  1063.  
  1064. return(0)
  1065.  
  1066.  
  1067.  /****************************************************************************
  1068. ************************* Show menu and wait for input ************************
  1069.  ****************************************************************************/
  1070.  
  1071. showmenu: interpret 'procedure expose 'globals
  1072.           parse arg menutitle, helpnode
  1073.  
  1074. if helpnode ~= '' & mvhelp then do
  1075.     rcnt = menulist.count
  1076.     rcnt = rcnt + 1; menulist.rcnt = ''
  1077.     rcnt = rcnt + 1; menulist.rcnt = 'HELP'
  1078.     menulist.count = rcnt
  1079. end
  1080.  
  1081. doit:
  1082.  
  1083. address(thorport)
  1084. 'REQUESTLIST INSTEM 'menulist' TITLE "'menutitle'" SIZEGADGET'
  1085. if (rc = 5) then i = 0
  1086. else if (rc ~= 0) then displayerror(rc, 'REQUESTLIST:\n'THOR.LASTERROR)
  1087. else do
  1088.     choice = result
  1089.     if choice = '' then signal doit
  1090.     if choice = 'HELP' then do; call help(helpnode); signal doit; end
  1091.  
  1092.     do i = 1 to menulist.count
  1093.         if choice = menulist.i then break
  1094.     end
  1095. end
  1096.  
  1097. drop menulist.
  1098.  
  1099. return(i)
  1100.  
  1101.  
  1102.  /****************************************************************************
  1103. ************************* Write configuration to disk *************************
  1104.  ****************************************************************************/
  1105.  
  1106. writecfg: interpret 'procedure expose 'globals
  1107.  
  1108. CDF_NOT_ON_BBS         = '00008000'x  /* This conference is not on the bbs. */
  1109. CDNT_NONET             = 0            /* This conference is a local conference. This is the default values for new conferences. */
  1110. CDNT_MAILFOLDER        = 3            /* This conference is a virtual mail folder */
  1111.  
  1112. cfgopen = open(cf, bbsdata.BBSPATH || cfgfile || '.new', 'W')
  1113.  
  1114. if cfgopen then do
  1115.     address(thorport)
  1116.     'OPENPROGRESS TITLE "CfgSortMail 'version'" TOTAL 'trigger.count' PT "Writing configuration..."'
  1117.     if rc ~= 0 then displayerror(rc, 'OPENPROGRESS:\n'THOR.LASTERROR)
  1118.     else progwin = result
  1119.  
  1120.     entry = 'GLOBAL CONFERENCE "'addasterix(globalcfg.CONFERENCE)'"'
  1121.     if symbol('globalcfg.LOCALTO') = 'VAR' then entry = entry' LOCALTO "'addasterix(globalcfg.LOCALTO)'"'
  1122.     call writeln(cf, entry)
  1123.     if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1124.     call writeln(cf, '')
  1125.     if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1126.  
  1127.     if trigger.count > 0 then do i = 1 to trigger.count
  1128.         'UPDATEPROGRESS 'progwin' CURRENT 'i
  1129.         if rc ~= 0 then displayerror(rc, 'UPDATEPROGRESS:\n'THOR.LASTERROR'\nError while writing new configuration file.\nThe previous configuration will be used instead.')
  1130.  
  1131.         entry = 'TRIGGER NAME "'addasterix(trigger.i.NAME)'"'
  1132.         if ~((symbol('trigger.i.groups') ~= 'VAR') | (trigger.i.groups = '') | (trigger.i.groups = 'EMail')) then entry = entry' GROUPS "'addasterix(trigger.i.GROUPS)'"'
  1133.         if trigger.i.delmsg   then entry = entry' DELMSG'
  1134.         if trigger.i.deluser  then entry = entry' DELUSER'
  1135.         if trigger.i.matchall then entry = entry' MATCHALL'
  1136.         if trigger.i.nolocal  then entry = entry' NOLOCAL'
  1137.  
  1138.         call writeln(cf, entry)
  1139.         if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1140.  
  1141.         if trigger.i.search.count > 0 then do j = 1 to trigger.i.search.count
  1142.             entry = 'SEARCH 'trigger.i.search.j.type
  1143.             if (symbol('trigger.i.search.j.not')        = 'VAR') & (trigger.i.search.j.not = 1) then entry = entry' NOT'
  1144.             if (symbol('trigger.i.search.j.criteria')   = 'VAR') then entry = entry' SUBSTR "'addasterix(trigger.i.search.j.criteria)'"'
  1145.             if (symbol('trigger.i.search.j.pattern')    = 'VAR') then entry = entry' PATTERN "'addasterix(trigger.i.search.j.pattern)'"'
  1146.             if (symbol('trigger.i.search.j.keyword')    = 'VAR') then entry = entry' KEYWORD "'addasterix(trigger.i.search.j.keyword)'"'
  1147.  
  1148.             call writeln(cf, entry)
  1149.             if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1150.         end
  1151.  
  1152.         if trigger.i.action.count > 0 then do j = 1 to trigger.i.action.count
  1153.             entry = 'ACTION 'trigger.i.action.j.type
  1154.             if symbol('trigger.i.action.j.destsys')    = 'VAR' then entry = entry' DESTSYS "'addasterix(trigger.i.action.j.destsys)'"'
  1155.             if symbol('trigger.i.action.j.destconf')   = 'VAR' then entry = entry' DESTCONF "'addasterix(trigger.i.action.j.destconf)'"'
  1156.             if symbol('trigger.i.action.j.replyaddr')  = 'VAR' then entry = entry' REPLYADDR "'addasterix(trigger.i.action.j.replyaddr)'"'
  1157.             if symbol('trigger.i.action.j.scriptname') = 'VAR' then entry = entry' SCRIPTNAME "'addasterix(trigger.i.action.j.scriptname)'"'
  1158.             if symbol('trigger.i.action.j.scriptopts') = 'VAR' then entry = entry' SCRIPTOPTS "'addasterix(trigger.i.action.j.scriptopts)'"'
  1159.             if symbol('trigger.i.action.j.filename')   = 'VAR' then entry = entry' FILENAME "'addasterix(trigger.i.action.j.filename)'"'
  1160.             if symbol('trigger.i.action.j.directory')  = 'VAR' then entry = entry' DIRECTORY "'addasterix(trigger.i.action.j.directory)'"'
  1161.             if symbol('trigger.i.action.j.substitute') = 'VAR' then entry = entry' SUBSTITUTE "'addasterix(trigger.i.action.j.substitute)'"'
  1162.             if symbol('trigger.i.action.j.with')       = 'VAR' then entry = entry' WITH "'addasterix(trigger.i.action.j.with)'"'
  1163.             if symbol('trigger.i.action.j.sendto')     = 'VAR' then entry = entry' SENDTO "'addasterix(trigger.i.action.j.sendto)'"'
  1164.             if symbol('trigger.i.action.j.subject')    = 'VAR' then entry = entry' SUBJECT "'addasterix(trigger.i.action.j.subject)'"'
  1165.             if symbol('trigger.i.action.j.textfile')   = 'VAR' then entry = entry' TEXTFILE "'addasterix(trigger.i.action.j.textfile)'"'
  1166.             if trigger.i.action.j.header     = 1 then entry = entry' HEADER'
  1167.             if trigger.i.action.j.append     = 1 then entry = entry' APPEND'
  1168.             if trigger.i.action.j.nobin      = 1 then entry = entry' NOBIN'
  1169.             if trigger.i.action.j.dontadd    = 1 then entry = entry' DONTADD'
  1170.             if trigger.i.action.j.checkdupes = 1 then entry = entry' CHECKDUPES'
  1171.             if trigger.i.action.j.nostats    = 1 then entry = entry' NOSTATS'
  1172.  
  1173.             call writeln(cf, entry)
  1174.             if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1175.  
  1176.             /* Create conference and set appropriate flags if necessary */
  1177.             if (symbol('trigger.i.action.j.destconf') = 'VAR') then do
  1178.                 newconf = trigger.i.action.j.destconf
  1179.                 if (symbol('trigger.i.action.j.destsys') = 'VAR') then newsys = trigger.i.action.j.destsys
  1180.                 else newsys = globalcfg.SYSTEM
  1181.  
  1182.                 address(bbsread)
  1183.                 'CONFIGCONF "'addasterix(newsys)'" "'addasterix(newconf)'"'
  1184.                 if (rc ~= 0) then call displayerror(rc, 'CONFIGCONF: 'BBSREAD.LASTERROR'\nError while writing new configuration file.\nThe previous configuration will be used instead.')
  1185.  
  1186.                 'GETCONFDATA "'addasterix(newsys)'" "'addasterix(newconf)'" STEM 'confdata
  1187.                 if (rc ~= 0) then call displayerror(rc, 'GETCONFDATA: 'BBSREAD.LASTERROR'\nError while writing new configuration file.\nThe previous configuration will be used instead.')
  1188.  
  1189.                 if (confdata.CONFNETTYPE = CDNT_NONET) then do
  1190.                     'CONFIGCONF "'addasterix(newsys)'" "'addasterix(newconf)'" CONFNETTYPE 'CDNT_MAILFOLDER' SET 'c2x(CDF_NOT_ON_BBS)
  1191.                     if (rc ~= 0) then call displayerror(rc, 'CONFIGCONF: 'BBSREAD.LASTERROR'\nError while writing new configuration file.\nThe previous configuration will be used instead.')
  1192.                 end
  1193.                 address(thorport)
  1194.             end
  1195.  
  1196.         end
  1197.         
  1198.         call writeln(cf, 'ENDTRIGGER')
  1199.         if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1200.         call writeln(cf, '')
  1201.         if rc ~= 0 then call displayerror(30, 'Error while writing new configuration file.\nThe previous configuration will be used instead.')
  1202.     end
  1203.  
  1204. end
  1205.  
  1206. call close(cf)
  1207.  
  1208. if (exists('C:Copy') & exists('C:Delete')) then do
  1209.     address(command)
  1210.     'Copy "'bbsdata.BBSPATH || cfgfile || '.new" TO "'bbsdata.BBSPATH || cfgfile'" CLONE QUIET'
  1211.     if (rc ~= 0) then displayerror(30, 'Failed to update configuration file.')
  1212.  
  1213.     'Delete "'bbsdata.BBSPATH || cfgfile || '.new" FORCE QUIET'
  1214.     if (rc ~= 0) then displayerror(30, 'Failed to delete temporary configuration file.')
  1215.     address(thorport)
  1216. end
  1217.  
  1218. if symbol('progwin') = 'VAR' then do
  1219.     'CLOSEPROGRESS REQ 'progwin
  1220.     if (rc ~= 0) then displayerror(rc, 'CLOSEPROGRESS:\n'THOR.LASTERROR)
  1221.     drop progwin
  1222. end
  1223.  
  1224. return(0)
  1225.  
  1226.  
  1227.  /****************************************************************************
  1228. *********************** Open and read configuration file **********************
  1229.  ****************************************************************************/
  1230.  
  1231. readcfg: interpret 'procedure expose 'globals
  1232.  
  1233. triggers = 0
  1234.  
  1235. cfgopen = open(cf, bbsdata.BBSPATH || cfgfile, 'R')
  1236.  
  1237. if cfgopen then do
  1238.     cfglength = seek(cf, 0, 'E'); call seek(cf, 0, 'B')
  1239.  
  1240.     address(thorport)
  1241.     'OPENPROGRESS TITLE "CfgSortMail 'version'" TOTAL 'cfglength' PT "Reading configuration..."'
  1242.     if rc ~= 0 then displayerror(rc, 'OPENPROGRESS:\n'THOR.LASTERROR'\nConfiguration not loaded.')
  1243.     else progwin = result
  1244.  
  1245.     cfgline = 0
  1246.  
  1247.     address(bbsread)
  1248.  
  1249.     do until seek(cf, 0) = cfglength
  1250.         entry = readln(cf); cfgline = cfgline + 1
  1251.  
  1252.         if symbol('progwin') = 'VAR' then do
  1253.             address(thorport)
  1254.             'UPDATEPROGRESS 'progwin' CURRENT 'seek(cf, 0)
  1255.             if rc ~= 0 then displayerror(rc, 'UPDATEPROGRESS:\n'THOR.LASTERROR'\nConfiguration not loaded.')
  1256.             address(bbsread)
  1257.         end
  1258.  
  1259.         select
  1260.             when upper(subword(entry, 1, 1)) = "SYSTEM" then do
  1261.                 call reqnotify('Found old 2.x configuration file.  SortMail has\nchanged the configuration file format in version\n3.0.  A new one will have to be created from scratch.', 'Ok')
  1262.                 signal nocfg
  1263.             end
  1264.  
  1265.             when upper(subword(entry, 1, 1)) = "GLOBAL" then do
  1266.                 'READARGS TEMPLATE "SYSTEM/K,CONFERENCE/A,STATISTICS/S,NOWARN/S,LOGINSTATE/S,LOCALTO/K" STEM 'globalcfg' CMDLINE 'subword(entry, 2)
  1267.                 if rc ~= 0 then do
  1268.                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration not loaded.')
  1269.                     signal nocfg
  1270.                 end
  1271.                 globalcfg.SYSTEM = cursys.BBSNAME
  1272.                 if left(globalcfg.CONFERENCE, 9) = 'GLOBALCFG' then globalcfg.CONFERENCE = 'EMail'
  1273.             end
  1274.  
  1275.             when upper(subword(entry, 1, 1)) = "TRIGGER" then do
  1276.                 triggers = triggers + 1
  1277.                 trigger.triggers.cmdline = subword(entry, 2)
  1278.                 trigger.triggers.DELMSG = 0; trigger.triggers.DELUSER = 0; trigger.triggers.MATCHALL = 0; trigger.triggers.NOLOCAL = 0; trigger.triggers.hitcount = 0; trigger.triggers.failcount = 0
  1279.                 'READARGS TEMPLATE "NAME/A,DELMSG/S,DELUSER/S,MATCHALL/S,NOLOCAL/S,GROUPS/K" STEM 'trigger.triggers' CMDLINE 'trigger.triggers.cmdline
  1280.                 if rc ~= 0 then do
  1281.                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration only partitially loaded.')
  1282.                     signal nocfg
  1283.                 end
  1284.                 if (symbol('trigger.triggers.groups') ~= 'VAR') then trigger.triggers.groups = 'EMail'
  1285.  
  1286.                 do
  1287.                     searches = 0; actions = 0
  1288.  
  1289.                     do until upper(subentry) = 'ENDTRIGGER'
  1290.                         subentry = readln(cf); cfgline = cfgline + 1
  1291.  
  1292.                         select
  1293.                             when upper(subword(subentry, 1, 1)) = 'ACTION' then do
  1294.                                 actions = actions + 1
  1295.                                 trigger.triggers.action.actions.checkdupes = 0; trigger.triggers.action.actions.dontadd = 0; trigger.triggers.action.actions.nostats = 0trigger.triggers.action.actions.header = 0; trigger.triggers.action.actions.append = 0; trigger.triggers.action.actions.nobin = 0; trigger.triggers.action.actions.hitcount = 0; trigger.triggers.action.actions.failcount = 0
  1296.                                 'READARGS TEMPLATE "TYPE/A,DESTCONF/K,DESTSYS/K,REPLYADDR/K,SCRIPTNAME/K,SCRIPTOPTS/K,FILENAME/K,DIRECTORY/K,SUBSTITUTE/K,WITH/K,SENDTO/K,TEXTFILE/K,SUBJECT/K,HEADER/S,APPEND/S,NOBIN/S,CHECKDUPES/S,DONTADD/S,NOSTATS/S" STEM 'trigger.triggers.action.actions' CMDLINE 'subword(subentry, 2)
  1297.                                 if rc ~= 0 then do
  1298.                                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration only partitially loaded.')
  1299.                                     signal nocfg
  1300.                                 end
  1301.                                 trigger.triggers.action.actions.type = upper(trigger.triggers.action.actions.type)
  1302.                                 if (trigger.triggers.action.actions.type = 'MAIL') & ((symbol('trigger.triggers.action.actions.destconf') ~= 'VAR') | (symbol('trigger.triggers.action.actions.subject') ~= 'VAR') | (symbol('trigger.triggers.action.actions.textfile') ~= 'VAR')) then do
  1303.                                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\nMAIL action type incorrectly configured.\nConfiguration only partitially loaded.')
  1304.                                     signal nocfg
  1305.                                 end
  1306.                                 if (trigger.triggers.action.actions.type = 'FORWARD') & ((symbol('trigger.triggers.action.actions.destconf') ~= 'VAR') | (symbol('trigger.triggers.action.actions.sendto') ~= 'VAR')) then do
  1307.                                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\nFORWARD action type incorrectly configured.\nConfiguration only partitially loaded.')
  1308.                                     signal nocfg
  1309.                                 end
  1310.                             end
  1311.  
  1312.                             when upper(subword(subentry, 1, 1)) = 'SEARCH' then do
  1313.                                 searches = searches + 1; trigger.triggers.search.searches.not = 0
  1314.                                 'READARGS TEMPLATE "TYPE/A,CRITERIA/K,SUBSTR/K,KEYWORD/K,PATTERN/K,NOT/S" STEM 'trigger.triggers.search.searches' CMDLINE 'subword(subentry, 2)
  1315.                                 if rc ~= 0 then do
  1316.                                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\n'BBSREAD.LASTERROR'\nConfiguration only partitially loaded.')
  1317.                                     signal nocfg
  1318.                                 end
  1319.                                 trigger.triggers.search.searches.type = upper(trigger.triggers.search.searches.type)
  1320.                                 if (symbol('trigger.triggers.search.searches.substr') = 'VAR') then do
  1321.                                     trigger.triggers.search.searches.criteria = trigger.triggers.search.searches.substr; drop trigger.triggers.search.searches.substr
  1322.                                 end
  1323.                                 if ((symbol('trigger.triggers.search.searches.criteria') ~= 'VAR') & (symbol('trigger.triggers.search.searches.pattern') ~= 'VAR') | (symbol('trigger.triggers.search.searches.criteria') = 'VAR') & (symbol('trigger.triggers.search.searches.pattern') = 'VAR')) then do
  1324.                                     call displayerror(10, 'Error in 'cfgfile' line 'cfgline':\nIllegal use of search parameters.\nConfiguration only partitially loaded.')
  1325.                                     signal nocfg
  1326.                                 end
  1327.                             end
  1328.  
  1329.                             when eof(cf) then do
  1330.                                 call displayerror(10, 'Premature end of configuration file 'cfgfile'\nConfiguration only partitially loaded.')
  1331.                                 signal nocfg
  1332.                             end
  1333.  
  1334.                             when upper(subword(subentry, 1, 1)) = 'TRIGGER' then do
  1335.                                 call displayerror(10, 'TRIGGER did not contain ENDTRIGGER in 'cfgfile' line 'cfgline'\nConfiguration only partitially loaded.')
  1336.                                 signal nocfg
  1337.                             end
  1338.  
  1339.                             otherwise nop
  1340.                         end
  1341.                     end
  1342.                 end
  1343.  
  1344.                 trigger.triggers.action.count = actions
  1345.                 trigger.triggers.search.count = searches
  1346.             end
  1347.  
  1348.             otherwise nop
  1349.         end
  1350.  
  1351.         trigger.count = triggers
  1352.     end
  1353. end
  1354. else do
  1355.     call reqnotify('Configuration file did not exist or\ncould not be opened.\n\nIf you are using SortMail for the first\ntime, please take a moment to read\nSortMail.guide which you can find in\nthe Docs directory.', 'Ok')
  1356.     signal nocfg
  1357. end
  1358.  
  1359. if (symbol('globalcfg.CONFERENCE') ~= 'VAR') then do
  1360.     globalcfg.SYSTEM     = cursys.BBSNAME
  1361.     globalcfg.CONFERENCE = 'EMail'
  1362. end
  1363.  
  1364. signal fini
  1365.  
  1366.  
  1367. nocfg:
  1368.  
  1369. drop globalcfg.; globalcfg.SYSTEM = cursys.BBSNAME; globalcfg.CONFERENCE = 'EMail'
  1370. drop trigger.; trigger.count = 0
  1371.  
  1372.  
  1373. fini:
  1374.  
  1375. if (cfgopen) then do
  1376.     call close(cf)
  1377.  
  1378.     address(thorport)
  1379.     if symbol('progwin') = 'VAR' then do
  1380.         'CLOSEPROGRESS REQ 'progwin
  1381.         if rc ~= 0 then displayerror(rc, 'CLOSEPROGRESS:\n'THOR.LASTERROR)
  1382.         drop progwin
  1383.     end
  1384. end
  1385.  
  1386. return(0)
  1387.  
  1388.  
  1389.  /****************************************************************************
  1390. *********************** Display error and return or quit **********************
  1391.  ****************************************************************************/
  1392.  
  1393. displayerror: interpret 'procedure expose 'globals
  1394.               parse arg returned, errmsg
  1395.  
  1396. address(thorport)
  1397.  
  1398. select
  1399.     when returned > 0 & returned < 20 then do
  1400.         'REQUESTNOTIFY "CfgSortMail returned 'returned':\n'errmsg'" "Continue|Abort"'
  1401.         if result = 0 then signal cleanup
  1402.     end
  1403.  
  1404.     when returned > 19 then do
  1405.         'REQUESTNOTIFY "CfgSortMail returned 'returned':\n'errmsg'" "Abort"'
  1406.         signal cleanup
  1407.     end
  1408.  
  1409.     otherwise nop
  1410. end
  1411.  
  1412. drop THOR.LASTERROR BBSREAD.LASTERROR
  1413.  
  1414. address(thorport)
  1415.  
  1416. return(0)
  1417.  
  1418.  
  1419.  /****************************************************************************
  1420. ****** Insert asterix (*) before double quotes (") and existing asterixes *****
  1421.  ****************************************************************************/
  1422.  
  1423. addasterix: interpret 'procedure expose 'globals
  1424.             parse arg str
  1425.  
  1426. if str = '' then return(str)
  1427.  
  1428. lastfound = 0; found = index(str, '*')
  1429. do while found > lastfound
  1430.     secondpart = substr(str, found + length('*'))
  1431.     firstpart = substr(str, 1, length(str) - length(substr(str, found)))
  1432.     str = firstpart || '**' || secondpart
  1433.     lastfound = found + length('**')
  1434.     found = index(str, '*', lastfound)
  1435. end
  1436.  
  1437. lastfound = 0; found = index(str, '"')
  1438. do while found > lastfound
  1439.     secondpart = substr(str, found + length('"'))
  1440.     firstpart = substr(str, 1, length(str) - length(substr(str, found)))
  1441.     str = firstpart || '*"' || secondpart
  1442.     lastfound = found + length('*"')
  1443.     found = index(str, '"', lastfound)
  1444. end
  1445.  
  1446. return(str)
  1447.  
  1448.  
  1449.  /****************************************************************************
  1450. ******************** Extract filename from a complete path ********************
  1451.  ****************************************************************************/
  1452.  
  1453. getfilepart: procedure
  1454.              parse arg path
  1455.  
  1456. filepart = path
  1457.  
  1458. if (index(path, '/') > 0) & (lastpos('/', path) ~= length(path)) then filepart = substr(path, (lastpos('/', path) + 1))
  1459. else (if index(path, ':') > 0) & (lastpos(':', path) ~= length(path)) then filepart = substr(path, (lastpos(':', path) + 1))
  1460.  
  1461. return(filepart)
  1462.  
  1463.  
  1464.  /****************************************************************************
  1465. ******************** Extract pathname from a complete path ********************
  1466.  ****************************************************************************/
  1467.  
  1468. getpathpart: procedure expose thorpath
  1469.              parse arg path
  1470.  
  1471. if index(path, '/') > 0 then path = substr(path, 1, lastpos('/', path))
  1472. else if index(path, ':') > 0 then path = substr(path, 1, lastpos(':', path))
  1473.  
  1474. return(path)
  1475.  
  1476.  
  1477.  /****************************************************************************
  1478. ******************** Copy the contents of a stem to another *******************
  1479.  ****************************************************************************/
  1480.  
  1481. copystem: interpret 'procedure expose 'globals
  1482.           parse arg stemtype, fromnr, tonr
  1483.  
  1484. if stemtype = 1 then do
  1485.     /* Copy contents of original trigger stem to temporary trigger stem
  1486.        Used when entering trigger configuration */
  1487.  
  1488.     temp.name         = trigger.fromnr.name 
  1489.     temp.delmsg       = trigger.fromnr.delmsg
  1490.     temp.deluser      = trigger.fromnr.deluser
  1491.     temp.matchall     = trigger.fromnr.matchall
  1492.     temp.nolocal      = trigger.fromnr.nolocal
  1493.     temp.groups       = trigger.fromnr.groups
  1494.     temp.action.count = trigger.fromnr.action.count
  1495.     temp.search.count = trigger.fromnr.search.count
  1496.     if trigger.fromnr.search.count > 0 then do i = 1 to trigger.fromnr.search.count
  1497.         temp.search.i.type       = trigger.fromnr.search.i.type
  1498.         if symbol('trigger.fromnr.search.i.keyword')    = 'VAR' then temp.search.i.keyword    = trigger.fromnr.search.i.keyword;    else drop temp.search.i.keyword
  1499.         if symbol('trigger.fromnr.search.i.pattern')    = 'VAR' then temp.search.i.pattern    = trigger.fromnr.search.i.pattern;    else drop temp.search.i.pattern
  1500.         if symbol('trigger.fromnr.search.i.criteria')   = 'VAR' then temp.search.i.criteria   = trigger.fromnr.search.i.criteria;   else drop temp.search.i.criteria
  1501.         if symbol('trigger.fromnr.search.i.not')        = 'VAR' then temp.search.i.not        = trigger.fromnr.search.i.not;        else drop temp.search.i.not
  1502.     end
  1503.     if trigger.fromnr.action.count > 0 then do i = 1 to trigger.fromnr.action.count
  1504.         temp.action.i.type = trigger.fromnr.action.i.type
  1505.         if symbol('trigger.fromnr.action.i.destconf')   = 'VAR' then temp.action.i.destconf   = trigger.fromnr.action.i.destconf;   else drop temp.action.i.destconf
  1506.         if symbol('trigger.fromnr.action.i.destsys')    = 'VAR' then temp.action.i.destsys    = trigger.fromnr.action.i.destsys;    else drop temp.action.i.destsys
  1507.         if symbol('trigger.fromnr.action.i.replyaddr')  = 'VAR' then temp.action.i.replyaddr  = trigger.fromnr.action.i.replyaddr;  else drop temp.action.i.replyaddr
  1508.         if symbol('trigger.fromnr.action.i.scriptname') = 'VAR' then temp.action.i.scriptname = trigger.fromnr.action.i.scriptname; else drop temp.action.i.scriptname
  1509.         if symbol('trigger.fromnr.action.i.scriptopts') = 'VAR' then temp.action.i.scriptopts = trigger.fromnr.action.i.scriptopts; else drop temp.action.i.scriptopts
  1510.         if symbol('trigger.fromnr.action.i.filename')   = 'VAR' then temp.action.i.filename   = trigger.fromnr.action.i.filename;   else drop temp.action.i.filename
  1511.         if symbol('trigger.fromnr.action.i.directory')  = 'VAR' then temp.action.i.directory  = trigger.fromnr.action.i.directory;  else drop temp.action.i.directory
  1512.         if symbol('trigger.fromnr.action.i.substitute') = 'VAR' then temp.action.i.substitute = trigger.fromnr.action.i.substitute; else drop temp.action.i.substitute
  1513.         if symbol('trigger.fromnr.action.i.with')       = 'VAR' then temp.action.i.with       = trigger.fromnr.action.i.with;       else drop temp.action.i.with
  1514.         if symbol('trigger.fromnr.action.i.header')     = 'VAR' then temp.action.i.header     = trigger.fromnr.action.i.header;     else drop temp.action.i.header
  1515.         if symbol('trigger.fromnr.action.i.append')     = 'VAR' then temp.action.i.append     = trigger.fromnr.action.i.append;     else drop temp.action.i.append
  1516.         if symbol('trigger.fromnr.action.i.nobin')      = 'VAR' then temp.action.i.nobin      = trigger.fromnr.action.i.nobin;      else drop temp.action.i.nobin
  1517.         if symbol('trigger.fromnr.action.i.checkdupes') = 'VAR' then temp.action.i.checkdupes = trigger.fromnr.action.i.checkdupes; else drop temp.action.i.checkdupes
  1518.         if symbol('trigger.fromnr.action.i.dontadd')    = 'VAR' then temp.action.i.dontadd    = trigger.fromnr.action.i.dontadd   ; else drop temp.action.i.dontadd 
  1519.         if symbol('trigger.fromnr.action.i.nostats')    = 'VAR' then temp.action.i.nostats    = trigger.fromnr.action.i.nostats   ; else drop temp.action.i.nostats 
  1520.         if symbol('trigger.fromnr.action.i.sendto')     = 'VAR' then temp.action.i.sendto     = trigger.fromnr.action.i.sendto    ; else drop temp.action.i.sendto
  1521.         if symbol('trigger.fromnr.action.i.subject')    = 'VAR' then temp.action.i.subject    = trigger.fromnr.action.i.subject   ; else drop temp.action.i.subject 
  1522.         if symbol('trigger.fromnr.action.i.textfile')   = 'VAR' then temp.action.i.textfile   = trigger.fromnr.action.i.textfile  ; else drop temp.action.i.textfile
  1523.     end
  1524. end
  1525.  
  1526. if stemtype = 2 then do
  1527.     /* Copy contents of action stem in temporary trigger stem to another temporary action stem
  1528.      Used when entering action configuration */
  1529.  
  1530.     atemp.type = temp.action.fromnr.type
  1531.     if symbol('temp.action.fromnr.destconf')   = 'VAR' then atemp.destconf   = temp.action.fromnr.destconf;   else drop atemp.destconf
  1532.     if symbol('temp.action.fromnr.destsys')    = 'VAR' then atemp.destsys    = temp.action.fromnr.destsys;    else drop atemp.destsys
  1533.     if symbol('temp.action.fromnr.replyaddr')  = 'VAR' then atemp.replyaddr  = temp.action.fromnr.replyaddr;  else drop atemp.replyaddr
  1534.     if symbol('temp.action.fromnr.scriptname') = 'VAR' then atemp.scriptname = temp.action.fromnr.scriptname; else drop atemp.scriptname
  1535.     if symbol('temp.action.fromnr.scriptopts') = 'VAR' then atemp.scriptopts = temp.action.fromnr.scriptopts; else drop atemp.scriptopts
  1536.     if symbol('temp.action.fromnr.filename')   = 'VAR' then atemp.filename   = temp.action.fromnr.filename;   else drop atemp.filename
  1537.     if symbol('temp.action.fromnr.directory')  = 'VAR' then atemp.directory  = temp.action.fromnr.directory;  else drop atemp.directory
  1538.     if symbol('temp.action.fromnr.substitute') = 'VAR' then atemp.substitute = temp.action.fromnr.substitute; else drop atemp.substitute
  1539.     if symbol('temp.action.fromnr.with')       = 'VAR' then atemp.with       = temp.action.fromnr.with;       else drop atemp.with
  1540.     if symbol('temp.action.fromnr.header')     = 'VAR' then atemp.header     = temp.action.fromnr.header;     else drop atemp.header
  1541.     if symbol('temp.action.fromnr.append')     = 'VAR' then atemp.append     = temp.action.fromnr.append;     else drop atemp.append
  1542.     if symbol('temp.action.fromnr.nobin')      = 'VAR' then atemp.nobin      = temp.action.fromnr.nobin;      else drop atemp.nobin
  1543.     if symbol('temp.action.fromnr.checkdupes') = 'VAR' then atemp.checkdupes = temp.action.fromnr.checkdupes; else drop atemp.checkdupes
  1544.     if symbol('temp.action.fromnr.dontadd')    = 'VAR' then atemp.dontadd    = temp.action.fromnr.dontadd   ; else drop atemp.dontadd
  1545.     if symbol('temp.action.fromnr.nostats')    = 'VAR' then atemp.nostats    = temp.action.fromnr.nostats   ; else drop atemp.nostats
  1546.     if symbol('temp.action.fromnr.sendto')     = 'VAR' then atemp.sendto     = temp.action.fromnr.sendto    ; else drop atemp.sendto 
  1547.     if symbol('temp.action.fromnr.subject')    = 'VAR' then atemp.subject    = temp.action.fromnr.subject   ; else drop atemp.subject
  1548.     if symbol('temp.action.fromnr.textfile')   = 'VAR' then atemp.textfile   = temp.action.fromnr.textfile  ; else drop atemp.textfile
  1549. end
  1550.  
  1551. if stemtype = 3 then do
  1552.     /* Copy contents of search stem in temporary trigger stem to another temporary search stem
  1553.      Used when entering search configuration */
  1554.  
  1555.     stemp.type     = temp.search.fromnr.type
  1556.     if symbol('temp.search.fromnr.keyword')    = 'VAR' then stemp.keyword    = temp.search.fromnr.keyword;    else drop stemp.keyword
  1557.     if symbol('temp.search.fromnr.pattern')    = 'VAR' then stemp.pattern    = temp.search.fromnr.pattern;    else drop stemp.pattern
  1558.     if symbol('temp.search.fromnr.criteria')   = 'VAR' then stemp.criteria   = temp.search.fromnr.criteria;   else drop stemp.criteria
  1559.     if symbol('temp.search.fromnr.not')        = 'VAR' then stemp.not        = temp.search.fromnr.not;        else drop stemp.not
  1560. end
  1561.  
  1562. if stemtype = 10 then do
  1563.     /* Copy contents of temporary trigger stem to original trigger stem
  1564.        Used when exiting trigger configuration */
  1565.  
  1566.     trigger.tonr.name         = temp.name
  1567.     trigger.tonr.delmsg       = temp.delmsg
  1568.     trigger.tonr.deluser      = temp.deluser
  1569.     trigger.tonr.matchall     = temp.matchall
  1570.     trigger.tonr.nolocal      = temp.nolocal
  1571.     trigger.tonr.groups       = temp.groups
  1572.     trigger.tonr.action.count = temp.action.count
  1573.     trigger.tonr.search.count = temp.search.count
  1574.     if temp.search.count > 0 then do i = 1 to temp.search.count
  1575.         trigger.tonr.search.i.type     = temp.search.i.type
  1576.         if symbol('temp.search.i.criteria')   = 'VAR' then trigger.tonr.search.i.criteria   = temp.search.i.criteria;   else drop trigger.tonr.search.i.criteria
  1577.         if symbol('temp.search.i.keyword')    = 'VAR' then trigger.tonr.search.i.keyword    = temp.search.i.keyword;    else drop trigger.tonr.search.i.keyword
  1578.         if symbol('temp.search.i.pattern')    = 'VAR' then trigger.tonr.search.i.pattern    = temp.search.i.pattern;    else drop trigger.tonr.search.i.pattern
  1579.         if symbol('temp.search.i.not')        = 'VAR' then trigger.tonr.search.i.not        = temp.search.i.not;        else drop trigger.tonr.search.i.not
  1580.     end
  1581.     if temp.action.count > 0 then do i = 1 to temp.action.count
  1582.         trigger.tonr.action.i.type     = temp.action.i.type
  1583.         if symbol('temp.action.i.destconf')   = 'VAR' then trigger.tonr.action.i.destconf   = temp.action.i.destconf;   else drop trigger.tonr.action.i.destconf
  1584.         if symbol('temp.action.i.destsys')    = 'VAR' then trigger.tonr.action.i.destsys    = temp.action.i.destsys;    else drop trigger.tonr.action.i.destsys
  1585.         if symbol('temp.action.i.replyaddr')  = 'VAR' then trigger.tonr.action.i.replyaddr  = temp.action.i.replyaddr;  else drop trigger.tonr.action.i.replyaddr
  1586.         if symbol('temp.action.i.scriptname') = 'VAR' then trigger.tonr.action.i.scriptname = temp.action.i.scriptname; else drop trigger.tonr.action.i.scriptname
  1587.         if symbol('temp.action.i.scriptopts') = 'VAR' then trigger.tonr.action.i.scriptopts = temp.action.i.scriptopts; else drop trigger.tonr.action.i.scriptopts
  1588.         if symbol('temp.action.i.filename')   = 'VAR' then trigger.tonr.action.i.filename   = temp.action.i.filename;   else drop trigger.tonr.action.i.filename
  1589.         if symbol('temp.action.i.directory')  = 'VAR' then trigger.tonr.action.i.directory  = temp.action.i.directory;  else drop trigger.tonr.action.i.directory
  1590.         if symbol('temp.action.i.substitute') = 'VAR' then trigger.tonr.action.i.substitute = temp.action.i.substitute; else drop trigger.tonr.action.i.substitute
  1591.         if symbol('temp.action.i.with')       = 'VAR' then trigger.tonr.action.i.with       = temp.action.i.with;       else drop trigger.tonr.action.i.with
  1592.         if symbol('temp.action.i.header')     = 'VAR' then trigger.tonr.action.i.header     = temp.action.i.header;     else drop trigger.tonr.action.i.header
  1593.         if symbol('temp.action.i.append')     = 'VAR' then trigger.tonr.action.i.append     = temp.action.i.append;     else drop trigger.tonr.action.i.append
  1594.         if symbol('temp.action.i.nobin')      = 'VAR' then trigger.tonr.action.i.nobin      = temp.action.i.nobin;      else drop trigger.tonr.action.i.nobin
  1595.         if symbol('temp.action.i.checkdupes') = 'VAR' then trigger.tonr.action.i.checkdupes = temp.action.i.checkdupes; else drop trigger.tonr.action.i.checkdupes
  1596.         if symbol('temp.action.i.dontadd')    = 'VAR' then trigger.tonr.action.i.dontadd    = temp.action.i.dontadd   ; else drop trigger.tonr.action.i.dontadd
  1597.         if symbol('temp.action.i.nostats')    = 'VAR' then trigger.tonr.action.i.nostats    = temp.action.i.nostats   ; else drop trigger.tonr.action.i.nostats
  1598.         if symbol('temp.action.i.sendto')     = 'VAR' then trigger.tonr.action.i.sendto     = temp.action.i.sendto    ; else drop trigger.tonr.action.i.sendto
  1599.         if symbol('temp.action.i.subject')    = 'VAR' then trigger.tonr.action.i.subject    = temp.action.i.subject   ; else drop trigger.tonr.action.i.subject
  1600.         if symbol('temp.action.i.textfile')   = 'VAR' then trigger.tonr.action.i.textfile   = temp.action.i.textfile  ; else drop trigger.tonr.action.i.textfile
  1601.     end
  1602. end
  1603.  
  1604. if stemtype = 11 then do
  1605.     /* Copy contents of temporary action stem to temporary trigger stem
  1606.        Used when exiting action configuration */
  1607.  
  1608.     temp.action.tonr.type = atemp.type
  1609.     if symbol('atemp.destconf')   = 'VAR' then temp.action.tonr.destconf   = atemp.destconf;   else drop temp.action.tonr.destconf
  1610.     if symbol('atemp.destsys')    = 'VAR' then temp.action.tonr.destsys    = atemp.destsys;    else drop temp.action.tonr.destsys
  1611.     if symbol('atemp.replyaddr')  = 'VAR' then temp.action.tonr.replyaddr  = atemp.replyaddr;  else drop temp.action.tonr.replyaddr
  1612.     if symbol('atemp.scriptname') = 'VAR' then temp.action.tonr.scriptname = atemp.scriptname; else drop temp.action.tonr.scriptname
  1613.     if symbol('atemp.scriptopts') = 'VAR' then temp.action.tonr.scriptopts = atemp.scriptopts; else drop temp.action.tonr.scriptopts
  1614.     if symbol('atemp.filename')   = 'VAR' then temp.action.tonr.filename   = atemp.filename;   else drop temp.action.tonr.filename
  1615.     if symbol('atemp.directory')  = 'VAR' then temp.action.tonr.directory  = atemp.directory;  else drop temp.action.tonr.directory
  1616.     if symbol('atemp.substitute') = 'VAR' then temp.action.tonr.substitute = atemp.substitute; else drop temp.action.tonr.substitute
  1617.     if symbol('atemp.with')       = 'VAR' then temp.action.tonr.with       = atemp.with;       else drop temp.action.tonr.with
  1618.     if symbol('atemp.header')     = 'VAR' then temp.action.tonr.header     = atemp.header;     else drop temp.action.tonr.header
  1619.     if symbol('atemp.append')     = 'VAR' then temp.action.tonr.append     = atemp.append;     else drop temp.action.tonr.append
  1620.     if symbol('atemp.nobin')      = 'VAR' then temp.action.tonr.nobin      = atemp.nobin;      else drop temp.action.tonr.nobin
  1621.     if symbol('atemp.checkdupes') = 'VAR' then temp.action.tonr.checkdupes = atemp.checkdupes; else drop temp.action.tonr.checkdupes
  1622.     if symbol('atemp.dontadd')    = 'VAR' then temp.action.tonr.dontadd    = atemp.dontadd   ; else drop temp.action.tonr.dontadd 
  1623.     if symbol('atemp.nostats')    = 'VAR' then temp.action.tonr.nostats    = atemp.nostats   ; else drop temp.action.tonr.nostats 
  1624.     if symbol('atemp.sendto')     = 'VAR' then temp.action.tonr.sendto     = atemp.sendto    ; else drop temp.action.tonr.sendto    
  1625.     if symbol('atemp.subject')    = 'VAR' then temp.action.tonr.subject    = atemp.subject   ; else drop temp.action.tonr.subject 
  1626.     if symbol('atemp.textfile')   = 'VAR' then temp.action.tonr.textfile   = atemp.textfile  ; else drop temp.action.tonr.textfile
  1627. end
  1628.  
  1629. if stemtype = 12 then do
  1630.     /* Copy contents of temporary search stem to temporary trigger stem
  1631.        Used when exiting action configuration */
  1632.  
  1633.     temp.search.tonr.type     = stemp.type
  1634.     if symbol('stemp.keyword')       = 'VAR' then temp.search.tonr.keyword    = stemp.keyword;    else drop temp.search.tonr.keyword
  1635.     if symbol('stemp.pattern')       = 'VAR' then temp.search.tonr.pattern    = stemp.pattern;    else drop temp.search.tonr.pattern
  1636.     if symbol('stemp.criteria')      = 'VAR' then temp.search.tonr.criteria   = stemp.criteria;   else drop temp.search.tonr.criteria
  1637.     if symbol('stemp.not')           = 'VAR' then temp.search.tonr.not        = stemp.not;        else drop temp.search.tonr.not
  1638. end    
  1639.  
  1640. if stemtype = 20 then do
  1641.     /* Copy contents of original trigger stem to a new stem number
  1642.        Used when deleting of trigger, main menu and when rearranging triggers */
  1643.  
  1644.     trigger.tonr.name         = trigger.fromnr.name 
  1645.     trigger.tonr.delmsg       = trigger.fromnr.delmsg
  1646.     trigger.tonr.deluser      = trigger.fromnr.deluser
  1647.     trigger.tonr.matchall     = trigger.fromnr.matchall
  1648.     trigger.tonr.nolocal      = trigger.fromnr.nolocal
  1649.     trigger.tonr.groups       = trigger.fromnr.groups
  1650.     trigger.tonr.action.count = trigger.fromnr.action.count
  1651.     trigger.tonr.search.count = trigger.fromnr.search.count
  1652.     if trigger.fromnr.search.count > 0 then do j = 1 to trigger.fromnr.search.count
  1653.         trigger.tonr.search.j.type       = trigger.fromnr.search.j.type
  1654.         if symbol('trigger.fromnr.search.j.keyword')    = 'VAR' then trigger.tonr.search.j.keyword    = trigger.fromnr.search.j.keyword;    else drop trigger.tonr.search.j.keyword
  1655.         if symbol('trigger.fromnr.search.j.pattern')    = 'VAR' then trigger.tonr.search.j.pattern    = trigger.fromnr.search.j.pattern;    else drop trigger.tonr.search.j.pattern
  1656.         if symbol('trigger.fromnr.search.j.criteria')   = 'VAR' then trigger.tonr.search.j.criteria   = trigger.fromnr.search.j.criteria;   else drop trigger.tonr.search.j.criteria
  1657.         if symbol('trigger.fromnr.search.j.not')        = 'VAR' then trigger.tonr.search.j.not        = trigger.fromnr.search.j.not;        else drop trigger.tonr.search.j.not
  1658.     end
  1659.     if trigger.fromnr.action.count > 0 then do j = 1 to trigger.fromnr.action.count
  1660.         trigger.tonr.action.j.type = trigger.fromnr.action.j.type
  1661.         if symbol('trigger.fromnr.action.j.destconf')   = 'VAR' then trigger.tonr.action.j.destconf   = trigger.fromnr.action.j.destconf;   else drop trigger.tonr.action.j.destconf
  1662.         if symbol('trigger.fromnr.action.j.destsys')    = 'VAR' then trigger.tonr.action.j.destsys    = trigger.fromnr.action.j.destsys;    else drop trigger.tonr.action.j.destsys
  1663.         if symbol('trigger.fromnr.action.j.replyaddr')  = 'VAR' then trigger.tonr.action.j.replyaddr  = trigger.fromnr.action.j.replyaddr;  else drop trigger.tonr.action.j.replyaddr
  1664.         if symbol('trigger.fromnr.action.j.scriptname') = 'VAR' then trigger.tonr.action.j.scriptname = trigger.fromnr.action.j.scriptname; else drop trigger.tonr.action.j.scriptname
  1665.         if symbol('trigger.fromnr.action.j.scriptopts') = 'VAR' then trigger.tonr.action.j.scriptopts = trigger.fromnr.action.j.scriptopts; else drop trigger.tonr.action.j.scriptopts
  1666.         if symbol('trigger.fromnr.action.j.filename')   = 'VAR' then trigger.tonr.action.j.filename   = trigger.fromnr.action.j.filename;   else drop trigger.tonr.action.j.filename
  1667.         if symbol('trigger.fromnr.action.j.directory')  = 'VAR' then trigger.tonr.action.j.directory  = trigger.fromnr.action.j.directory;  else drop trigger.tonr.action.j.directory
  1668.         if symbol('trigger.fromnr.action.j.substitute') = 'VAR' then trigger.tonr.action.j.substitute = trigger.fromnr.action.j.substitute; else drop trigger.tonr.action.j.substitute
  1669.         if symbol('trigger.fromnr.action.j.with')       = 'VAR' then trigger.tonr.action.j.with       = trigger.fromnr.action.j.with;       else drop trigger.tonr.action.j.with
  1670.         if symbol('trigger.fromnr.action.j.header')     = 'VAR' then trigger.tonr.action.j.header     = trigger.fromnr.action.j.header;     else drop trigger.tonr.action.j.header
  1671.         if symbol('trigger.fromnr.action.j.append')     = 'VAR' then trigger.tonr.action.j.append     = trigger.fromnr.action.j.append;     else drop trigger.tonr.action.j.append
  1672.         if symbol('trigger.fromnr.action.j.nobin')      = 'VAR' then trigger.tonr.action.j.nobin      = trigger.fromnr.action.j.nobin;      else drop trigger.tonr.action.j.nobin
  1673.         if symbol('trigger.fromnr.action.j.checkdupes') = 'VAR' then trigger.tonr.action.j.checkdupes = trigger.fromnr.action.j.checkdupes; else drop trigger.tonr.action.j.checkdupes
  1674.         if symbol('trigger.fromnr.action.j.dontadd')    = 'VAR' then trigger.tonr.action.j.dontadd    = trigger.fromnr.action.j.dontadd   ; else drop trigger.tonr.action.j.dontadd 
  1675.         if symbol('trigger.fromnr.action.j.nostats')    = 'VAR' then trigger.tonr.action.j.nostats    = trigger.fromnr.action.j.nostats   ; else drop trigger.tonr.action.j.nostats 
  1676.         if symbol('trigger.fromnr.action.j.sendto')     = 'VAR' then trigger.tonr.action.j.sendto     = trigger.fromnr.action.j.sendto    ; else drop trigger.tonr.action.j.sendto    
  1677.         if symbol('trigger.fromnr.action.j.subject')    = 'VAR' then trigger.tonr.action.j.subject    = trigger.fromnr.action.j.subject   ; else drop trigger.tonr.action.j.subject 
  1678.         if symbol('trigger.fromnr.action.j.textfile')   = 'VAR' then trigger.tonr.action.j.textfile   = trigger.fromnr.action.j.textfile  ; else drop trigger.tonr.action.j.textfile
  1679.     end
  1680. end
  1681.  
  1682. if stemtype = 21 then do
  1683.     /* Copy contents of temporary action stem to a new action stem number
  1684.      Used when deleting action stem */
  1685.  
  1686.     temp.action.tonr.type = temp.action.fromnr.type
  1687.     if symbol('temp.action.fromnr.destconf')   = 'VAR' then temp.action.tonr.destconf   = temp.action.fromnr.destconf;   else drop temp.action.tonr.destconf
  1688.     if symbol('temp.action.fromnr.destsys')    = 'VAR' then temp.action.tonr.destsys    = temp.action.fromnr.destsys;    else drop temp.action.tonr.destsys
  1689.     if symbol('temp.action.fromnr.replyaddr')  = 'VAR' then temp.action.tonr.replyaddr  = temp.action.fromnr.replyaddr;  else drop temp.action.tonr.replyaddr
  1690.     if symbol('temp.action.fromnr.scriptname') = 'VAR' then temp.action.tonr.scriptname = temp.action.fromnr.scriptname; else drop temp.action.tonr.scriptname
  1691.     if symbol('temp.action.fromnr.scriptopts') = 'VAR' then temp.action.tonr.scriptopts = temp.action.fromnr.scriptopts; else drop temp.action.tonr.scriptopts
  1692.     if symbol('temp.action.fromnr.filename')   = 'VAR' then temp.action.tonr.filename   = temp.action.fromnr.filename;   else drop temp.action.tonr.filename
  1693.     if symbol('temp.action.fromnr.directory')  = 'VAR' then temp.action.tonr.directory  = temp.action.fromnr.directory;  else drop temp.action.tonr.directory
  1694.     if symbol('temp.action.fromnr.substitute') = 'VAR' then temp.action.tonr.substitute = temp.action.fromnr.substitute; else drop temp.action.tonr.substitute
  1695.     if symbol('temp.action.fromnr.with')       = 'VAR' then temp.action.tonr.with       = temp.action.fromnr.with;       else drop temp.action.tonr.with
  1696.     if symbol('temp.action.fromnr.header')     = 'VAR' then temp.action.tonr.header     = temp.action.fromnr.header;     else drop temp.action.tonr.header
  1697.     if symbol('temp.action.fromnr.append')     = 'VAR' then temp.action.tonr.append     = temp.action.fromnr.append;     else drop temp.action.tonr.append
  1698.     if symbol('temp.action.fromnr.nobin')      = 'VAR' then temp.action.tonr.nobin      = temp.action.fromnr.nobin;      else drop temp.action.tonr.nobin
  1699.     if symbol('temp.action.fromnr.checkdupes') = 'VAR' then temp.action.tonr.checkdupes = temp.action.fromnr.checkdupes; else drop temp.action.tonr.checkdupes
  1700.     if symbol('temp.action.fromnr.dontadd')    = 'VAR' then temp.action.tonr.dontadd    = temp.action.fromnr.dontadd   ; else drop temp.action.tonr.dontadd 
  1701.     if symbol('temp.action.fromnr.nostats')    = 'VAR' then temp.action.tonr.nostats    = temp.action.fromnr.nostats   ; else drop temp.action.tonr.nostats 
  1702.     if symbol('temp.action.fromnr.sendto')     = 'VAR' then temp.action.tonr.sendto     = temp.action.fromnr.sendto    ; else drop temp.action.tonr.sendto    
  1703.     if symbol('temp.action.fromnr.subject')    = 'VAR' then temp.action.tonr.subject    = temp.action.fromnr.subject   ; else drop temp.action.tonr.subject 
  1704.     if symbol('temp.action.fromnr.textfile')   = 'VAR' then temp.action.tonr.textfile   = temp.action.fromnr.textfile  ; else drop temp.action.tonr.textfile 
  1705. end
  1706.  
  1707. if stemtype = 22 then do
  1708.     /* Copy contents of temporary search stem to a new search stem number
  1709.      Used when deleting search stem */
  1710.  
  1711.     temp.search.tonr.type     = temp.search.fromnr.type
  1712.     if symbol('temp.search.fromnr.keyword')    = 'VAR' then temp.search.tonr.keyword    = temp.search.fromnr.keyword;    else drop temp.search.tonr.keyword
  1713.     if symbol('temp.search.fromnr.pattern')    = 'VAR' then temp.search.tonr.pattern    = temp.search.fromnr.pattern;    else drop temp.search.tonr.pattern
  1714.     if symbol('temp.search.fromnr.criteria')   = 'VAR' then temp.search.tonr.criteria   = temp.search.fromnr.criteria;   else drop temp.search.tonr.criteria
  1715.     if symbol('temp.search.fromnr.not')        = 'VAR' then temp.search.tonr.not        = temp.search.fromnr.not;        else drop temp.search.tonr.not
  1716. end
  1717.  
  1718. return(0)
  1719.  
  1720.  
  1721.  /****************************************************************************
  1722. *********************** Pop up a notification requester ***********************
  1723.  ****************************************************************************/
  1724.  
  1725. reqnotify: interpret 'procedure expose 'globals
  1726.            parse arg body, choices
  1727.  
  1728. if choices = '' then choices = 'Yes|No'
  1729.  
  1730. address(thorport)
  1731. 'REQUESTNOTIFY "'addasterix(body)'" "'choices'"'
  1732. if rc ~= 0 then call displayerror(rc, 'REQUESTNOTIFY:\n'THOR.LASTERROR)
  1733.  
  1734. return(result)
  1735.  
  1736.  
  1737.  /****************************************************************************
  1738. ************************** Display a string requester *************************
  1739.  ****************************************************************************/
  1740.  
  1741. reqstring: interpret 'procedure expose 'globals
  1742.            parse arg reqtitle, reqbody, reqid, maxchar, reqmode, pickup, pattern
  1743.  
  1744. orgreqid = reqid
  1745.  
  1746. if pattern = '' then pattern = 0
  1747.  
  1748. showreq:
  1749.  
  1750. choices = 'Ok'
  1751. if (pattern)  then choices = '_String|_Pattern'
  1752. if (pickup ~= '') then choices = choices'|Pick_up'
  1753. choices = choices'|Cancel'
  1754.  
  1755. if (symbol('msginfo') = 'VAR') then reqid = msginfo
  1756.  
  1757. address(thorport)
  1758. 'REQUESTSTRING "'reqtitle'" BODY "'addasterix(reqbody)'" ID "'reqid'" BT "'choices'" MAXCHARS 'maxchar
  1759. if ((pattern) & ~(thorrc = 3)) | (~(pattern) & ~(thorrc = 2)) then select
  1760.     when (rc = 5 | result = '') & (reqmode = 0 | reqmode = '') then return(orgreqid)
  1761.     when (rc = 5 | result = '') & (reqmode = 1) then return('')
  1762.     when (rc > 5) then call displayerror(rc, 'REQUESTSTRING:\n'THOR.LASTERROR)
  1763.     when (pattern) then do
  1764.         if (thorrc = 1) then return('01'x || result)
  1765.         if (thorrc = 2) then return('02'x || result)
  1766.     end
  1767.     when ~(pattern) then if (thorrc = 1) then return(result)
  1768.     otherwise nop
  1769. end
  1770.  
  1771. if (symbol('pickup') = 'VAR') & (pickup ~= '') then do
  1772.     msginfo = readmsg(pickup)
  1773.     if (msginfo = 0) then drop msginfo
  1774.     signal showreq
  1775.     end
  1776. else return(result)
  1777.  
  1778.  
  1779.  /****************************************************************************
  1780. *************************** Display a file requester **************************
  1781.  ****************************************************************************/
  1782.  
  1783. reqfile: interpret 'procedure expose 'globals
  1784.          parse arg reqtitle, pathpart, filepart, reqpattern
  1785.  
  1786. address(thorport)
  1787. if (pathpart = '') then do
  1788.     'GETGLOBALCONFIG STEM 'thorcfg
  1789.     if (rc ~= 0) then call displayerror(rc, 'GETGLOBALCONFIG:\n'THOR.LASTERROR)
  1790.  
  1791.     if (right(thorcfg.SAVEDIR, 1) = '/') | (right(thorcfg.SAVEDIR, 1) = ':') then pathpart = thorcfg.SAVEDIR; else pathpart = thorcfg.SAVEDIR || '/'
  1792. end
  1793.  
  1794. 'REQUESTFILE "'reqtitle'" ID "'pathpart'" IF "'filepart'" PAT "'reqpattern'" FP'
  1795. select
  1796.     when (rc = 5) then return('')
  1797.     when (rc > 5) then call displayerror(rc, 'REQUESTFILE:\n'THOR.LASTERROR)
  1798.     otherwise return(result)
  1799. end
  1800.  
  1801.  
  1802.  /****************************************************************************
  1803. ************** Read data from message currently displayed in Thor *************
  1804.  ****************************************************************************/
  1805.  
  1806. readmsg: interpret 'procedure expose 'globals
  1807.          parse arg field
  1808.  
  1809. address(thorport)
  1810. 'CURRENTMSG 'currentmsg
  1811. if rc ~= 0 then do
  1812.     call reqnotify('CURRENTMSG:\n'THOR.LASTERROR, 'Ok')
  1813.     return(0)
  1814. end
  1815.  
  1816. select
  1817.     when (field = 'replyaddr') | (field = 'REPLYTO') then do
  1818.         address(bbsread)
  1819.         'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' TEXTSTEM 'text
  1820.         if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR)
  1821.         retfield = value('text.replyaddr')
  1822.         if retfield = upper('text.replyaddr') then do; drop text.; return(0); end
  1823.     end
  1824.  
  1825.     when field = 'headline' then do
  1826.         address(bbsread)
  1827.         'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' TEXTSTEM 'text
  1828.         if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR)
  1829.         if (symbol('text.comment.count') ~= 'VAR') | (text.comment.count = 0) then do
  1830.             drop text.
  1831.             call reqnotify('There are no headers in the current message.', 'Ok')
  1832.             return(0)
  1833.         end
  1834.  
  1835.         address(thorport)
  1836.         'REQUESTLIST INSTEM 'text.comment' TITLE "Select header line" SIZEGADGET'
  1837.         if rc = 5 then do; drop text.; return(0); end
  1838.         if rc > 0 then do; call displayerror(rc, 'REQUESTLIST:\n'THOR.LASTERROR); drop text.; return(0); end
  1839.         retfield = subword(result, 1, 1)
  1840.     end
  1841.  
  1842.     otherwise do
  1843.         address(bbsread)
  1844.         'READBRMESSAGE "'currentmsg.BBSNAME'" "'currentmsg.CONFNAME'" 'currentmsg.MSGNR' HEADSTEM 'head
  1845.         if rc ~= 0 then call displayerror(rc, 'READBRMESSAGE:\n'BBSREAD.LASTERROR)
  1846.         retfield = value('head.'field)
  1847.         if retfield = upper('head.'field) then do; drop head.; return(0); end
  1848.     end
  1849. end
  1850.  
  1851. drop text. head.
  1852. return(retfield)
  1853.  
  1854.  
  1855.  /****************************************************************************
  1856. ********************** Display help using OS3.1+ MultiView ********************
  1857.  ****************************************************************************/
  1858.  
  1859. help: procedure expose thorport thorpath
  1860.             parse arg node
  1861.  
  1862. if index(show('P',,), 'MV_SORTMAIL') = 0 then do
  1863.     address(thorport)
  1864.     'GETGLOBALCONFIG STEM 'globcfg
  1865.  
  1866.     address(command)
  1867.     'Run >NIL: MultiView FILE 'thorpath'Docs/SortMail.guide PORTNAME MV_SORTMAIL PUBSCREEN 'GLOBCFG.PUBSCREENNAME
  1868.     'WaitForPort MV_SORTMAIL'
  1869.     if rc ~= 0 then do
  1870.         address(thorport)
  1871.         'REQUESTNOTIFY "Couldn''t start MultiView." "Sigh"'
  1872.         return(0)
  1873.     end
  1874. end
  1875.  
  1876. address(MV_SORTMAIL)
  1877. 'DOTRIGGERMETHOD CONTENTS'
  1878. 'DOTRIGGERMETHOD CONTENTS'
  1879. do i = 1 to node; 'DOTRIGGERMETHOD NEXT'; end
  1880.  
  1881. address(thorport)
  1882. return(0)
  1883.  
  1884.  
  1885.  /****************************************************************************
  1886. ********************* Display a listview with all systems *********************
  1887.  ****************************************************************************/
  1888.  
  1889. getsystem: intepret 'procedure expose 'globals
  1890.  
  1891. address(thorport)
  1892. do i = 1 to bbslist.count; menulist.i = center(i'.', 4) || bbslist.i; end; menulist.count = bbslist.count
  1893. syschoice = showmenu('Select destination system')
  1894.  
  1895. return(syschoice)
  1896.  
  1897.  
  1898.  /****************************************************************************
  1899. ******************* Display a listview with all conferences *******************
  1900.  ****************************************************************************/
  1901.  
  1902. getconference: interpret 'procedure expose 'globals
  1903.                parse arg system, createconf
  1904.  
  1905. address(bbsread)
  1906. 'GETCONFLIST BBSNAME "'system'" STEM 'conflist
  1907. if (rc > 0) then call displayerror(20, 'GETCONFLIST:\n'BBSREAD.LASTERROR)
  1908.  
  1909. if (conflist.count = 0) then do
  1910.     reqnotify('No conferences created on this\nsystem. Using EMail as default.', 'Ok')
  1911.     atemp.destconf = 'EMail'
  1912.     return(2)
  1913. end
  1914.  
  1915. mcnt = 0
  1916.  
  1917. if (createconf = 1) then do
  1918.     mcnt = mcnt + 1; menulist.mcnt = 'Create new conference             '
  1919.     mcnt = mcnt + 1; menulist.mcnt = ''
  1920. end
  1921.  
  1922. do i = 1 to conflist.count
  1923.     mcnt = mcnt + 1; menulist.mcnt = center(i'.', 4) || conflist.i
  1924. end
  1925. menulist.count = mcnt
  1926.  
  1927. confchoice = showmenu('Select destination conference')
  1928.  
  1929. select
  1930.     when ((createconf = 1) & (confchoice = 1)) then atemp.destconf = reqstring('Destination conference', 'Enter new conference name.\nThe new conference will be created\nby SortMail when the first message\nis copied to it.', addasterix(atemp.destconf), 64)
  1931.     when ((createconf = 1) & (confchoice > 2)) then do; cno = confchoice - 2; atemp.destconf = conflist.cno; end
  1932.     when ((createconf = 0) & (confchoice > 0)) then atemp.destconf = conflist.confchoice
  1933.     otherwise nop
  1934. end
  1935.  
  1936. return(confchoice)
  1937.